Compare commits
No commits in common. "8e315642a4a99209ffa28d35f890560e75795187" and "ee6e7324b206775581a7507ff06bd33fcfa4eb4f" have entirely different histories.
8e315642a4
...
ee6e7324b2
2
.idea/Passport.iml
generated
2
.idea/Passport.iml
generated
@ -6,7 +6,5 @@
|
|||||||
<orderEntry type="inheritedJdk" />
|
<orderEntry type="inheritedJdk" />
|
||||||
<orderEntry type="sourceFolder" forTests="false" />
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
<orderEntry type="library" name="animate.css" level="application" />
|
<orderEntry type="library" name="animate.css" level="application" />
|
||||||
<orderEntry type="library" name="tailwindcss" level="application" />
|
|
||||||
<orderEntry type="library" name="@tailwindcss/typography" level="application" />
|
|
||||||
</component>
|
</component>
|
||||||
</module>
|
</module>
|
2
.idea/jsLibraryMappings.xml
generated
2
.idea/jsLibraryMappings.xml
generated
@ -1,6 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<project version="4">
|
<project version="4">
|
||||||
<component name="JavaScriptLibraryMappings">
|
<component name="JavaScriptLibraryMappings">
|
||||||
<file url="PROJECT" libraries="{@tailwindcss/typography, animate.css, tailwindcss}" />
|
<file url="PROJECT" libraries="{animate.css}" />
|
||||||
</component>
|
</component>
|
||||||
</project>
|
</project>
|
37
.idea/workspace.xml
generated
37
.idea/workspace.xml
generated
@ -4,17 +4,28 @@
|
|||||||
<option name="autoReloadType" value="ALL" />
|
<option name="autoReloadType" value="ALL" />
|
||||||
</component>
|
</component>
|
||||||
<component name="ChangeListManager">
|
<component name="ChangeListManager">
|
||||||
<list default="true" id="3fefb2c4-b6f9-466b-a523-53352e8d6f95" name="更改" comment=":sparkles: User center page">
|
<list default="true" id="3fefb2c4-b6f9-466b-a523-53352e8d6f95" name="更改" comment=":sparkles: Sign up & Sign in">
|
||||||
<change afterPath="$PROJECT_DIR$/pkg/server/ui/personalize.go" afterDir="false" />
|
<change afterPath="$PROJECT_DIR$/pkg/server/ui/accounts.go" afterDir="false" />
|
||||||
<change afterPath="$PROJECT_DIR$/pkg/views/users/personalize.gohtml" afterDir="false" />
|
<change afterPath="$PROJECT_DIR$/pkg/server/ui/mfa.go" afterDir="false" />
|
||||||
|
<change afterPath="$PROJECT_DIR$/pkg/services/mfa.go" afterDir="false" />
|
||||||
|
<change afterPath="$PROJECT_DIR$/pkg/views/mfa-apply.gohtml" afterDir="false" />
|
||||||
|
<change afterPath="$PROJECT_DIR$/pkg/views/mfa.gohtml" afterDir="false" />
|
||||||
|
<change afterPath="$PROJECT_DIR$/pkg/views/users/me/index.gohtml" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/pkg/i18n/locale.en.json" beforeDir="false" afterPath="$PROJECT_DIR$/pkg/i18n/locale.en.json" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/pkg/i18n/locale.en.json" beforeDir="false" afterPath="$PROJECT_DIR$/pkg/i18n/locale.en.json" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/pkg/i18n/locale.zh.json" beforeDir="false" afterPath="$PROJECT_DIR$/pkg/i18n/locale.zh.json" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/pkg/i18n/locale.zh.json" beforeDir="false" afterPath="$PROJECT_DIR$/pkg/i18n/locale.zh.json" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/pkg/server/startup.go" beforeDir="false" afterPath="$PROJECT_DIR$/pkg/server/startup.go" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/pkg/server/ui/index.go" beforeDir="false" afterPath="$PROJECT_DIR$/pkg/server/ui/index.go" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/pkg/server/ui/index.go" beforeDir="false" afterPath="$PROJECT_DIR$/pkg/server/ui/index.go" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/pkg/server/ui/signin.go" beforeDir="false" afterPath="$PROJECT_DIR$/pkg/server/ui/signin.go" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/pkg/server/ui/signup.go" beforeDir="false" afterPath="$PROJECT_DIR$/pkg/server/ui/signup.go" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/pkg/services/factors.go" beforeDir="false" afterPath="$PROJECT_DIR$/pkg/services/factors.go" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/pkg/services/ticket.go" beforeDir="false" afterPath="$PROJECT_DIR$/pkg/services/ticket.go" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/pkg/services/ticket_token.go" beforeDir="false" afterPath="$PROJECT_DIR$/pkg/services/ticket_token.go" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/pkg/utils/request.go" beforeDir="false" afterPath="$PROJECT_DIR$/pkg/utils/request.go" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/pkg/views/layouts/auth.gohtml" beforeDir="false" afterPath="$PROJECT_DIR$/pkg/views/layouts/auth.gohtml" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/pkg/views/layouts/auth.gohtml" beforeDir="false" afterPath="$PROJECT_DIR$/pkg/views/layouts/auth.gohtml" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/pkg/views/layouts/user-center.gohtml" beforeDir="false" afterPath="$PROJECT_DIR$/pkg/views/layouts/user-center.gohtml" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/pkg/views/signin.gohtml" beforeDir="false" afterPath="$PROJECT_DIR$/pkg/views/signin.gohtml" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/pkg/views/signup.gohtml" beforeDir="false" afterPath="$PROJECT_DIR$/pkg/views/signup.gohtml" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/pkg/views/signup.gohtml" beforeDir="false" afterPath="$PROJECT_DIR$/pkg/views/signup.gohtml" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/pkg/views/users/me.gohtml" beforeDir="false" afterPath="$PROJECT_DIR$/pkg/views/users/me.gohtml" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/settings.toml" beforeDir="false" afterPath="$PROJECT_DIR$/settings.toml" afterDir="false" />
|
||||||
</list>
|
</list>
|
||||||
<option name="SHOW_DIALOG" value="false" />
|
<option name="SHOW_DIALOG" value="false" />
|
||||||
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
||||||
@ -82,22 +93,10 @@
|
|||||||
<recent name="$PROJECT_DIR$/pkg" />
|
<recent name="$PROJECT_DIR$/pkg" />
|
||||||
</key>
|
</key>
|
||||||
<key name="MoveFile.RECENT_KEYS">
|
<key name="MoveFile.RECENT_KEYS">
|
||||||
<recent name="$PROJECT_DIR$/pkg/views/users" />
|
|
||||||
<recent name="$PROJECT_DIR$/pkg/utils" />
|
<recent name="$PROJECT_DIR$/pkg/utils" />
|
||||||
<recent name="$PROJECT_DIR$/pkg/services" />
|
<recent name="$PROJECT_DIR$/pkg/services" />
|
||||||
</key>
|
</key>
|
||||||
</component>
|
</component>
|
||||||
<component name="RunAnythingCache">
|
|
||||||
<myKeys>
|
|
||||||
<visibility group="Grunt" flag="true" />
|
|
||||||
<visibility group="Gulp" flag="true" />
|
|
||||||
<visibility group="HTTP 请求" flag="true" />
|
|
||||||
<visibility group="Recent projects" flag="true" />
|
|
||||||
<visibility group="Run configurations" flag="true" />
|
|
||||||
<visibility group="npm" flag="true" />
|
|
||||||
<visibility group="yarn" flag="true" />
|
|
||||||
</myKeys>
|
|
||||||
</component>
|
|
||||||
<component name="RunManager">
|
<component name="RunManager">
|
||||||
<configuration name="Backend" type="GoApplicationRunConfiguration" factoryName="Go Application">
|
<configuration name="Backend" type="GoApplicationRunConfiguration" factoryName="Go Application">
|
||||||
<module name="Passport" />
|
<module name="Passport" />
|
||||||
@ -136,9 +135,7 @@
|
|||||||
<MESSAGE value=":recycle: Refactor frontend" />
|
<MESSAGE value=":recycle: Refactor frontend" />
|
||||||
<MESSAGE value=":sparkles: New ticket ways" />
|
<MESSAGE value=":sparkles: New ticket ways" />
|
||||||
<MESSAGE value=":sparkles: Sign up & Sign in" />
|
<MESSAGE value=":sparkles: Sign up & Sign in" />
|
||||||
<MESSAGE value=":sparkles: An entire complete sign in user flow" />
|
<option name="LAST_COMMIT_MESSAGE" value=":sparkles: Sign up & Sign in" />
|
||||||
<MESSAGE value=":sparkles: User center page" />
|
|
||||||
<option name="LAST_COMMIT_MESSAGE" value=":sparkles: User center page" />
|
|
||||||
</component>
|
</component>
|
||||||
<component name="VgoProject">
|
<component name="VgoProject">
|
||||||
<settings-migrated>true</settings-migrated>
|
<settings-migrated>true</settings-migrated>
|
||||||
|
1
go.mod
1
go.mod
@ -45,7 +45,6 @@ require (
|
|||||||
github.com/gofiber/utils v1.1.0 // indirect
|
github.com/gofiber/utils v1.1.0 // indirect
|
||||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
||||||
github.com/golang/protobuf v1.5.3 // indirect
|
github.com/golang/protobuf v1.5.3 // indirect
|
||||||
github.com/gomarkdown/markdown v0.0.0-20240419095408-642f0ee99ae2 // indirect
|
|
||||||
github.com/google/s2a-go v0.1.7 // indirect
|
github.com/google/s2a-go v0.1.7 // indirect
|
||||||
github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect
|
github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect
|
||||||
github.com/googleapis/gax-go/v2 v2.12.0 // indirect
|
github.com/googleapis/gax-go/v2 v2.12.0 // indirect
|
||||||
|
2
go.sum
2
go.sum
@ -86,8 +86,6 @@ github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw
|
|||||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||||
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
|
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
|
||||||
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||||
github.com/gomarkdown/markdown v0.0.0-20240419095408-642f0ee99ae2 h1:yEt5djSYb4iNtmV9iJGVday+i4e9u6Mrn5iP64HH5QM=
|
|
||||||
github.com/gomarkdown/markdown v0.0.0-20240419095408-642f0ee99ae2/go.mod h1:JDGcbDT52eL4fju3sZ4TeHGsQwhG9nbDV21aMyhwPoA=
|
|
||||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||||
|
@ -5,8 +5,6 @@
|
|||||||
"nickname": "Nickname",
|
"nickname": "Nickname",
|
||||||
"password": "Password",
|
"password": "Password",
|
||||||
"unknown": "Unknown",
|
"unknown": "Unknown",
|
||||||
"apply": "Apply",
|
|
||||||
"back": "Back",
|
|
||||||
"magicToken": "Magic Token",
|
"magicToken": "Magic Token",
|
||||||
"signinTitle": "Sign In",
|
"signinTitle": "Sign In",
|
||||||
"signinCaption": "Sign in to Solarpass to explore entire Solar Network. Explore posts, discover communities, talk with your best friends. All these things in the Solar Network!",
|
"signinCaption": "Sign in to Solarpass to explore entire Solar Network. Explore posts, discover communities, talk with your best friends. All these things in the Solar Network!",
|
||||||
|
@ -5,8 +5,6 @@
|
|||||||
"nickname": "昵称",
|
"nickname": "昵称",
|
||||||
"password": "密码",
|
"password": "密码",
|
||||||
"unknown": "未知",
|
"unknown": "未知",
|
||||||
"apply": "应用",
|
|
||||||
"back": "返回",
|
|
||||||
"magicToken": "魔法令牌",
|
"magicToken": "魔法令牌",
|
||||||
"signinTitle": "登陆",
|
"signinTitle": "登陆",
|
||||||
"signinCaption": "登陆 Solarpass 以探索整个 Solar Network,浏览帖子、探索社区、和你的好朋友聊八卦,一切尽在 Solar Network!",
|
"signinCaption": "登陆 Solarpass 以探索整个 Solar Network,浏览帖子、探索社区、和你的好朋友聊八卦,一切尽在 Solar Network!",
|
||||||
|
@ -23,8 +23,8 @@ type Account struct {
|
|||||||
PersonalPage AccountPage `json:"personal_page"`
|
PersonalPage AccountPage `json:"personal_page"`
|
||||||
Contacts []AccountContact `json:"contacts"`
|
Contacts []AccountContact `json:"contacts"`
|
||||||
|
|
||||||
Tickets []AuthTicket `json:"tickets"`
|
Sessions []AuthTicket `json:"sessions"`
|
||||||
Factors []AuthFactor `json:"factors"`
|
Factors []AuthFactor `json:"factors"`
|
||||||
|
|
||||||
Events []ActionEvent `json:"events"`
|
Events []ActionEvent `json:"events"`
|
||||||
MagicTokens []MagicToken `json:"-" gorm:"foreignKey:AssignTo"`
|
MagicTokens []MagicToken `json:"-" gorm:"foreignKey:AssignTo"`
|
||||||
|
@ -45,20 +45,20 @@ type AuthTicket struct {
|
|||||||
|
|
||||||
func (v AuthTicket) IsAvailable() error {
|
func (v AuthTicket) IsAvailable() error {
|
||||||
if v.RequireMFA || v.RequireAuthenticate {
|
if v.RequireMFA || v.RequireAuthenticate {
|
||||||
return fmt.Errorf("ticket isn't authenticated yet")
|
return fmt.Errorf("session isn't authenticated yet")
|
||||||
}
|
}
|
||||||
if v.AvailableAt != nil && time.Now().Unix() < v.AvailableAt.Unix() {
|
if v.AvailableAt != nil && time.Now().Unix() < v.AvailableAt.Unix() {
|
||||||
return fmt.Errorf("ticket isn't available yet")
|
return fmt.Errorf("session isn't available yet")
|
||||||
}
|
}
|
||||||
if v.ExpiredAt != nil && time.Now().Unix() > v.ExpiredAt.Unix() {
|
if v.ExpiredAt != nil && time.Now().Unix() > v.ExpiredAt.Unix() {
|
||||||
return fmt.Errorf("ticket expired")
|
return fmt.Errorf("session expired")
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type AuthContext struct {
|
type AuthContext struct {
|
||||||
Ticket AuthTicket `json:"ticket"`
|
Ticket AuthTicket `json:"session"`
|
||||||
Account Account `json:"account"`
|
Account Account `json:"account"`
|
||||||
ExpiredAt time.Time `json:"expired_at"`
|
ExpiredAt time.Time `json:"expired_at"`
|
||||||
}
|
}
|
||||||
|
@ -11,7 +11,7 @@ type ThirdClient struct {
|
|||||||
Secret string `json:"secret"`
|
Secret string `json:"secret"`
|
||||||
Urls datatypes.JSONSlice[string] `json:"urls"`
|
Urls datatypes.JSONSlice[string] `json:"urls"`
|
||||||
Callbacks datatypes.JSONSlice[string] `json:"callbacks"`
|
Callbacks datatypes.JSONSlice[string] `json:"callbacks"`
|
||||||
Sessions []AuthTicket `json:"tickets" gorm:"foreignKey:ClientID"`
|
Sessions []AuthTicket `json:"sessions" gorm:"foreignKey:ClientID"`
|
||||||
Notifications []Notification `json:"notifications" gorm:"foreignKey:SenderID"`
|
Notifications []Notification `json:"notifications" gorm:"foreignKey:SenderID"`
|
||||||
IsDraft bool `json:"is_draft"`
|
IsDraft bool `json:"is_draft"`
|
||||||
AccountID *uint `json:"account_id"`
|
AccountID *uint `json:"account_id"`
|
||||||
|
@ -113,7 +113,7 @@ func editUserinfo(c *fiber.Ctx) error {
|
|||||||
|
|
||||||
func killSession(c *fiber.Ctx) error {
|
func killSession(c *fiber.Ctx) error {
|
||||||
user := c.Locals("principal").(models.Account)
|
user := c.Locals("principal").(models.Account)
|
||||||
id, _ := c.ParamsInt("ticketId", 0)
|
id, _ := c.ParamsInt("sessionId", 0)
|
||||||
|
|
||||||
if err := database.C.Delete(&models.AuthTicket{}, &models.AuthTicket{
|
if err := database.C.Delete(&models.AuthTicket{}, &models.AuthTicket{
|
||||||
BaseModel: models.BaseModel{ID: uint(id)},
|
BaseModel: models.BaseModel{ID: uint(id)},
|
||||||
|
@ -28,29 +28,29 @@ func preConnect(c *fiber.Ctx) error {
|
|||||||
|
|
||||||
user := c.Locals("principal").(models.Account)
|
user := c.Locals("principal").(models.Account)
|
||||||
|
|
||||||
var ticket models.AuthTicket
|
var session models.AuthTicket
|
||||||
if err := database.C.Where(&models.AuthTicket{
|
if err := database.C.Where(&models.AuthTicket{
|
||||||
AccountID: user.ID,
|
AccountID: user.ID,
|
||||||
ClientID: &client.ID,
|
ClientID: &client.ID,
|
||||||
}).Where("last_grant_at IS NULL").First(&ticket).Error; err == nil {
|
}).Where("last_grant_at IS NULL").First(&session).Error; err == nil {
|
||||||
if ticket.ExpiredAt != nil && ticket.ExpiredAt.Unix() < time.Now().Unix() {
|
if session.ExpiredAt != nil && session.ExpiredAt.Unix() < time.Now().Unix() {
|
||||||
return c.JSON(fiber.Map{
|
return c.JSON(fiber.Map{
|
||||||
"client": client,
|
"client": client,
|
||||||
"ticket": nil,
|
"session": nil,
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
ticket, err = services.RegenSession(ticket)
|
session, err = services.RegenSession(session)
|
||||||
}
|
}
|
||||||
|
|
||||||
return c.JSON(fiber.Map{
|
return c.JSON(fiber.Map{
|
||||||
"client": client,
|
"client": client,
|
||||||
"ticket": ticket,
|
"session": session,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return c.JSON(fiber.Map{
|
return c.JSON(fiber.Map{
|
||||||
"client": client,
|
"client": client,
|
||||||
"ticket": nil,
|
"session": nil,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -86,7 +86,7 @@ func doConnect(c *fiber.Ctx) error {
|
|||||||
} else {
|
} else {
|
||||||
services.AddEvent(user, "oauth.connect", client.Alias, c.IP(), c.Get(fiber.HeaderUserAgent))
|
services.AddEvent(user, "oauth.connect", client.Alias, c.IP(), c.Get(fiber.HeaderUserAgent))
|
||||||
return c.JSON(fiber.Map{
|
return c.JSON(fiber.Map{
|
||||||
"ticket": ticket,
|
"session": ticket,
|
||||||
"redirect_uri": redirect,
|
"redirect_uri": redirect,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -12,7 +12,7 @@ func getTickets(c *fiber.Ctx) error {
|
|||||||
offset := c.QueryInt("offset", 0)
|
offset := c.QueryInt("offset", 0)
|
||||||
|
|
||||||
var count int64
|
var count int64
|
||||||
var tickets []models.AuthTicket
|
var sessions []models.AuthTicket
|
||||||
if err := database.C.
|
if err := database.C.
|
||||||
Where(&models.AuthTicket{AccountID: user.ID}).
|
Where(&models.AuthTicket{AccountID: user.ID}).
|
||||||
Model(&models.AuthTicket{}).
|
Model(&models.AuthTicket{}).
|
||||||
@ -25,12 +25,12 @@ func getTickets(c *fiber.Ctx) error {
|
|||||||
Where(&models.AuthTicket{AccountID: user.ID}).
|
Where(&models.AuthTicket{AccountID: user.ID}).
|
||||||
Limit(take).
|
Limit(take).
|
||||||
Offset(offset).
|
Offset(offset).
|
||||||
Find(&tickets).Error; err != nil {
|
Find(&sessions).Error; err != nil {
|
||||||
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
|
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
return c.JSON(fiber.Map{
|
return c.JSON(fiber.Map{
|
||||||
"count": count,
|
"count": count,
|
||||||
"data": tickets,
|
"data": sessions,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -88,7 +88,7 @@ func NewServer() {
|
|||||||
me.Put("/page", authMiddleware, editPersonalPage)
|
me.Put("/page", authMiddleware, editPersonalPage)
|
||||||
me.Get("/events", authMiddleware, getEvents)
|
me.Get("/events", authMiddleware, getEvents)
|
||||||
me.Get("/tickets", authMiddleware, getTickets)
|
me.Get("/tickets", authMiddleware, getTickets)
|
||||||
me.Delete("/tickets/:ticketId", authMiddleware, killSession)
|
me.Delete("/sessions/:sessionId", authMiddleware, killSession)
|
||||||
|
|
||||||
me.Post("/confirm", doRegisterConfirm)
|
me.Post("/confirm", doRegisterConfirm)
|
||||||
|
|
||||||
|
@ -1,48 +1,7 @@
|
|||||||
package ui
|
package ui
|
||||||
|
|
||||||
import (
|
import "github.com/gofiber/fiber/v2"
|
||||||
"fmt"
|
|
||||||
"git.solsynth.dev/hydrogen/passport/pkg/database"
|
|
||||||
"git.solsynth.dev/hydrogen/passport/pkg/models"
|
|
||||||
"github.com/gofiber/fiber/v2"
|
|
||||||
"github.com/gomarkdown/markdown"
|
|
||||||
"github.com/gomarkdown/markdown/html"
|
|
||||||
"github.com/gomarkdown/markdown/parser"
|
|
||||||
"github.com/sujit-baniya/flash"
|
|
||||||
"html/template"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
func selfUserinfoPage(c *fiber.Ctx) error {
|
func selfUserinfoPage(c *fiber.Ctx) error {
|
||||||
user := c.Locals("principal").(models.Account)
|
return c.Render("views/users/me/index", fiber.Map{})
|
||||||
|
|
||||||
var data models.Account
|
|
||||||
if err := database.C.
|
|
||||||
Where(&models.Account{BaseModel: models.BaseModel{ID: user.ID}}).
|
|
||||||
Preload("Profile").
|
|
||||||
Preload("PersonalPage").
|
|
||||||
Preload("Contacts").
|
|
||||||
First(&data).Error; err != nil {
|
|
||||||
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
var birthday = "Unknown"
|
|
||||||
if data.Profile.Birthday != nil {
|
|
||||||
birthday = data.Profile.Birthday.Format(time.RFC822)
|
|
||||||
}
|
|
||||||
|
|
||||||
doc := parser.
|
|
||||||
NewWithExtensions(parser.CommonExtensions | parser.AutoHeadingIDs | parser.NoEmptyLineBeforeBlock).
|
|
||||||
Parse([]byte(data.PersonalPage.Content))
|
|
||||||
|
|
||||||
renderer := html.NewRenderer(html.RendererOptions{Flags: html.CommonFlags | html.HrefTargetBlank})
|
|
||||||
|
|
||||||
return c.Render("views/users/me", fiber.Map{
|
|
||||||
"info": flash.Get(c)["message"],
|
|
||||||
"uid": fmt.Sprintf("%08d", data.ID),
|
|
||||||
"joined_at": data.CreatedAt.Format(time.RFC822),
|
|
||||||
"birthday_at": birthday,
|
|
||||||
"personal_page": template.HTML(markdown.Render(doc, renderer)),
|
|
||||||
"userinfo": data,
|
|
||||||
}, "views/layouts/user-center")
|
|
||||||
}
|
}
|
||||||
|
@ -12,10 +12,12 @@ func MapUserInterface(A *fiber.App, authFunc func(c *fiber.Ctx, overrides ...str
|
|||||||
if cookie := c.Cookies(services.CookieAccessKey); len(cookie) > 0 {
|
if cookie := c.Cookies(services.CookieAccessKey); len(cookie) > 0 {
|
||||||
token = cookie
|
token = cookie
|
||||||
}
|
}
|
||||||
|
fmt.Println(token)
|
||||||
|
|
||||||
c.Locals("token", token)
|
c.Locals("token", token)
|
||||||
|
|
||||||
if err := authFunc(c); err != nil {
|
if err := authFunc(c); err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
uri := c.Request().URI().FullURI()
|
uri := c.Request().URI().FullURI()
|
||||||
return c.Redirect(fmt.Sprintf("/sign-in?redirect_uri=%s", string(uri)))
|
return c.Redirect(fmt.Sprintf("/sign-in?redirect_uri=%s", string(uri)))
|
||||||
} else {
|
} else {
|
||||||
@ -36,7 +38,4 @@ func MapUserInterface(A *fiber.App, authFunc func(c *fiber.Ctx, overrides ...str
|
|||||||
pages.Post("/mfa/apply", mfaApplyAction)
|
pages.Post("/mfa/apply", mfaApplyAction)
|
||||||
|
|
||||||
pages.Get("/users/me", authCheckWare, selfUserinfoPage)
|
pages.Get("/users/me", authCheckWare, selfUserinfoPage)
|
||||||
pages.Get("/users/me/personalize", authCheckWare, personalizePage)
|
|
||||||
|
|
||||||
pages.Post("/users/me/personalize", authCheckWare, personalizeAction)
|
|
||||||
}
|
}
|
||||||
|
@ -1,98 +0,0 @@
|
|||||||
package ui
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"git.solsynth.dev/hydrogen/passport/pkg/database"
|
|
||||||
"git.solsynth.dev/hydrogen/passport/pkg/models"
|
|
||||||
"git.solsynth.dev/hydrogen/passport/pkg/utils"
|
|
||||||
"github.com/gofiber/fiber/v2"
|
|
||||||
"github.com/nicksnyder/go-i18n/v2/i18n"
|
|
||||||
"github.com/samber/lo"
|
|
||||||
"github.com/sujit-baniya/flash"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
func personalizePage(c *fiber.Ctx) error {
|
|
||||||
user := c.Locals("principal").(models.Account)
|
|
||||||
localizer := c.Locals("localizer").(*i18n.Localizer)
|
|
||||||
|
|
||||||
var data models.Account
|
|
||||||
if err := database.C.
|
|
||||||
Where(&models.Account{BaseModel: models.BaseModel{ID: user.ID}}).
|
|
||||||
Preload("Profile").
|
|
||||||
Preload("PersonalPage").
|
|
||||||
Preload("Contacts").
|
|
||||||
First(&data).Error; err != nil {
|
|
||||||
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
var birthday any
|
|
||||||
if data.Profile.Birthday != nil {
|
|
||||||
birthday = strings.SplitN(data.Profile.Birthday.Format(time.RFC3339), "T", 1)[0]
|
|
||||||
}
|
|
||||||
|
|
||||||
apply, _ := localizer.LocalizeMessage(&i18n.Message{ID: "apply"})
|
|
||||||
back, _ := localizer.LocalizeMessage(&i18n.Message{ID: "back"})
|
|
||||||
|
|
||||||
return c.Render("views/users/personalize", fiber.Map{
|
|
||||||
"info": flash.Get(c)["message"],
|
|
||||||
"birthday_at": birthday,
|
|
||||||
"userinfo": data,
|
|
||||||
"i18n": fiber.Map{
|
|
||||||
"apply": apply,
|
|
||||||
"back": back,
|
|
||||||
},
|
|
||||||
}, "views/layouts/user-center")
|
|
||||||
}
|
|
||||||
|
|
||||||
func personalizeAction(c *fiber.Ctx) error {
|
|
||||||
user := c.Locals("principal").(models.Account)
|
|
||||||
|
|
||||||
var data struct {
|
|
||||||
Nick string `form:"nick" validate:"required,min=4,max=24"`
|
|
||||||
Description string `form:"description"`
|
|
||||||
FirstName string `form:"first_name"`
|
|
||||||
LastName string `form:"last_name"`
|
|
||||||
Birthday string `form:"birthday"`
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := utils.BindAndValidate(c, &data); err != nil {
|
|
||||||
return flash.WithInfo(c, fiber.Map{
|
|
||||||
"message": err.Error(),
|
|
||||||
}).Redirect("/users/me/personalize")
|
|
||||||
}
|
|
||||||
|
|
||||||
var account models.Account
|
|
||||||
if err := database.C.
|
|
||||||
Where(&models.Account{BaseModel: models.BaseModel{ID: user.ID}}).
|
|
||||||
Preload("Profile").
|
|
||||||
First(&account).Error; err != nil {
|
|
||||||
return flash.WithInfo(c, fiber.Map{
|
|
||||||
"message": fmt.Sprintf("unable to get your userinfo: %v", err),
|
|
||||||
}).Redirect("/users/me/personalize")
|
|
||||||
}
|
|
||||||
|
|
||||||
account.Nick = data.Nick
|
|
||||||
account.Description = data.Description
|
|
||||||
account.Profile.FirstName = data.FirstName
|
|
||||||
account.Profile.LastName = data.LastName
|
|
||||||
|
|
||||||
if birthday, err := time.Parse(time.DateOnly, data.Birthday); err == nil {
|
|
||||||
account.Profile.Birthday = lo.ToPtr(birthday)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := database.C.Save(&account).Error; err != nil {
|
|
||||||
return flash.WithInfo(c, fiber.Map{
|
|
||||||
"message": fmt.Sprintf("unable to personalize your account: %v", err),
|
|
||||||
}).Redirect("/users/me/personalize")
|
|
||||||
} else if err := database.C.Save(&account.Profile).Error; err != nil {
|
|
||||||
return flash.WithInfo(c, fiber.Map{
|
|
||||||
"message": fmt.Sprintf("unable to personalize your profile: %v", err),
|
|
||||||
}).Redirect("/users/me/personalize")
|
|
||||||
}
|
|
||||||
|
|
||||||
return flash.WithInfo(c, fiber.Map{
|
|
||||||
"message": "your account has been personalized",
|
|
||||||
}).Redirect("/users/me")
|
|
||||||
}
|
|
@ -12,7 +12,7 @@ func getOtherUserinfo(c *fiber.Ctx) error {
|
|||||||
var account models.Account
|
var account models.Account
|
||||||
if err := database.C.
|
if err := database.C.
|
||||||
Where(&models.Account{Name: alias}).
|
Where(&models.Account{Name: alias}).
|
||||||
Omit("tickets", "challenges", "factors", "events", "clients", "notifications", "notify_subscribers").
|
Omit("sessions", "challenges", "factors", "events", "clients", "notifications", "notify_subscribers").
|
||||||
Preload("Profile").
|
Preload("Profile").
|
||||||
First(&account).Error; err != nil {
|
First(&account).Error; err != nil {
|
||||||
return fiber.NewError(fiber.StatusBadRequest, err.Error())
|
return fiber.NewError(fiber.StatusBadRequest, err.Error())
|
||||||
|
@ -85,14 +85,14 @@ func GrantAuthContext(jti string) (models.AuthContext, error) {
|
|||||||
var ctx models.AuthContext
|
var ctx models.AuthContext
|
||||||
|
|
||||||
// Query data from primary database
|
// Query data from primary database
|
||||||
ticket, err := GetTicketWithToken(jti)
|
session, err := GetTicketWithToken(jti)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ctx, fmt.Errorf("invalid auth ticket: %v", err)
|
return ctx, fmt.Errorf("invalid auth session: %v", err)
|
||||||
} else if err := ticket.IsAvailable(); err != nil {
|
} else if err := session.IsAvailable(); err != nil {
|
||||||
return ctx, fmt.Errorf("unavailable auth ticket: %v", err)
|
return ctx, fmt.Errorf("unavailable auth session: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
user, err := GetAccount(ticket.AccountID)
|
user, err := GetAccount(session.AccountID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ctx, fmt.Errorf("invalid account: %v", err)
|
return ctx, fmt.Errorf("invalid account: %v", err)
|
||||||
}
|
}
|
||||||
@ -100,7 +100,7 @@ func GrantAuthContext(jti string) (models.AuthContext, error) {
|
|||||||
// Every context should expires in some while
|
// Every context should expires in some while
|
||||||
// Once user update their account info, this will have delay to update
|
// Once user update their account info, this will have delay to update
|
||||||
ctx = models.AuthContext{
|
ctx = models.AuthContext{
|
||||||
Ticket: ticket,
|
Ticket: session,
|
||||||
Account: user,
|
Account: user,
|
||||||
ExpiredAt: time.Now().Add(5 * time.Minute),
|
ExpiredAt: time.Now().Add(5 * time.Minute),
|
||||||
}
|
}
|
||||||
|
@ -14,7 +14,7 @@ func DoAutoSignoff() {
|
|||||||
duration := time.Duration(viper.GetInt64("security.auto_signoff_duration")) * time.Second
|
duration := time.Duration(viper.GetInt64("security.auto_signoff_duration")) * time.Second
|
||||||
divider := time.Now().Add(-duration)
|
divider := time.Now().Add(-duration)
|
||||||
|
|
||||||
log.Debug().Time("before", divider).Msg("Now signing off tickets...")
|
log.Debug().Time("before", divider).Msg("Now signing off sessions...")
|
||||||
|
|
||||||
if tx := database.C.
|
if tx := database.C.
|
||||||
Where("last_grant_at < ?", divider).
|
Where("last_grant_at < ?", divider).
|
||||||
|
@ -45,7 +45,7 @@ func NewTicket(user models.Account, ip, ua string) (models.AuthTicket, error) {
|
|||||||
UserAgent: ua,
|
UserAgent: ua,
|
||||||
RequireMFA: requireMFA,
|
RequireMFA: requireMFA,
|
||||||
RequireAuthenticate: true,
|
RequireAuthenticate: true,
|
||||||
ExpiredAt: nil,
|
ExpiredAt: lo.ToPtr(time.Now().Add(2 * time.Hour)),
|
||||||
AvailableAt: nil,
|
AvailableAt: nil,
|
||||||
AccountID: user.ID,
|
AccountID: user.ID,
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
<div class="wrapper-container">
|
<div class="wrapper-container">
|
||||||
<div class="wrapper-middleware">
|
<div class="wrapper-middleware">
|
||||||
{{if ne .info nil}}
|
{{if ne .info nil}}
|
||||||
<div class="alert">
|
<div class="animate__animated animate__fadeInDown alert">
|
||||||
<div class="content">{{.info}}</div>
|
<div class="content">{{.info}}</div>
|
||||||
</div>
|
</div>
|
||||||
{{end}}
|
{{end}}
|
||||||
@ -26,8 +26,6 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
||||||
background-color: var(--md-sys-color-surface-container);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.wrapper-middleware {
|
.wrapper-middleware {
|
||||||
@ -107,12 +105,6 @@
|
|||||||
display: unset;
|
display: unset;
|
||||||
}
|
}
|
||||||
|
|
||||||
.columns-two {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 1fr 1fr;
|
|
||||||
gap: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (min-width: 768px) {
|
@media (min-width: 768px) {
|
||||||
.wrapper-card {
|
.wrapper-card {
|
||||||
grid-template-columns: 1fr 1fr;
|
grid-template-columns: 1fr 1fr;
|
||||||
|
@ -1,143 +0,0 @@
|
|||||||
<!doctype html>
|
|
||||||
<html lang="en">
|
|
||||||
|
|
||||||
{{template "views/partials/header"}}
|
|
||||||
|
|
||||||
<body>
|
|
||||||
<div class="wrapper-container">
|
|
||||||
<div class="wrapper-middleware">
|
|
||||||
{{if ne .info nil}}
|
|
||||||
<div class="alert">
|
|
||||||
<div class="content">{{.info}}</div>
|
|
||||||
</div>
|
|
||||||
{{end}}
|
|
||||||
|
|
||||||
<div class="wrapper-card">
|
|
||||||
{{embed}}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.wrapper-container {
|
|
||||||
width: 100dvw;
|
|
||||||
min-height: 100dvh;
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
overflow: auto;
|
|
||||||
|
|
||||||
scrollbar-width: none;
|
|
||||||
|
|
||||||
background-color: var(--md-sys-color-surface-container);
|
|
||||||
}
|
|
||||||
|
|
||||||
.wrapper-container::-webkit-scrollbar, body::-webkit-scrollbar {
|
|
||||||
display: none;
|
|
||||||
width: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.wrapper-middleware {
|
|
||||||
width: 100%;
|
|
||||||
min-width: 0;
|
|
||||||
max-width: min(800px, 100dvw);
|
|
||||||
|
|
||||||
margin: 1rem;
|
|
||||||
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.wrapper-card {
|
|
||||||
transition: all .3s;
|
|
||||||
height: auto;
|
|
||||||
overflow: auto;
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 1fr;
|
|
||||||
justify-content: center;
|
|
||||||
border-radius: 28px;
|
|
||||||
padding: 56px;
|
|
||||||
gap: 2rem;
|
|
||||||
background-color: var(--md-sys-color-surface);
|
|
||||||
color: var(--md-sys-color-on-surface)
|
|
||||||
}
|
|
||||||
|
|
||||||
.alert {
|
|
||||||
padding: 16px;
|
|
||||||
border-radius: 16px;
|
|
||||||
background-color: var(--md-sys-color-secondary-container);
|
|
||||||
color: var(--md-sys-color-on-secondary-container);
|
|
||||||
display: flex;
|
|
||||||
gap: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.alert .content {
|
|
||||||
flex-grow: 1;
|
|
||||||
text-transform: capitalize;
|
|
||||||
}
|
|
||||||
|
|
||||||
.logo {
|
|
||||||
margin-left: -8px;
|
|
||||||
margin-bottom: -8px;
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.title {
|
|
||||||
margin-block-start: 0.33em;
|
|
||||||
margin-block-end: 0.33em;
|
|
||||||
font-size: 2.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.caption {
|
|
||||||
font-size: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.action-form {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 0.8rem 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.action-form-buttons {
|
|
||||||
display: flex;
|
|
||||||
justify-content: end;
|
|
||||||
margin-top: 8px;
|
|
||||||
gap: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.block-field {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.responsive-hidden {
|
|
||||||
display: unset;
|
|
||||||
}
|
|
||||||
|
|
||||||
.columns-two {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 1fr 1fr;
|
|
||||||
gap: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.divider {
|
|
||||||
margin: 1rem 0.2rem;
|
|
||||||
border-top: 1px solid var(--md-sys-color-on-surface);
|
|
||||||
border-left: none !important;
|
|
||||||
border-right: none !important;
|
|
||||||
border-bottom: none !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (min-width: 768px) {
|
|
||||||
.wrapper-card {
|
|
||||||
grid-template-columns: 1fr 1fr;
|
|
||||||
}
|
|
||||||
|
|
||||||
.responsive-title-gap {
|
|
||||||
height: calc(56px + 0.44rem);
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</html>
|
|
@ -9,9 +9,10 @@
|
|||||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||||
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;500;700&display=swap" rel="stylesheet">
|
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;500;700&display=swap" rel="stylesheet">
|
||||||
|
|
||||||
<link
|
<link
|
||||||
href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@20..48,100..700,0..1,-50..200"
|
|
||||||
rel="stylesheet"
|
rel="stylesheet"
|
||||||
|
href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/4.1.1/animate.min.css"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<script type="importmap">
|
<script type="importmap">
|
||||||
@ -72,14 +73,6 @@
|
|||||||
--md-sys-color-on-error-container: #410002;
|
--md-sys-color-on-error-container: #410002;
|
||||||
}
|
}
|
||||||
|
|
||||||
.material-symbols-outlined {
|
|
||||||
font-variation-settings:
|
|
||||||
'FILL' 0,
|
|
||||||
'wght' 400,
|
|
||||||
'GRAD' 0,
|
|
||||||
'opsz' 24
|
|
||||||
}
|
|
||||||
|
|
||||||
html, body {
|
html, body {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
@ -64,4 +64,12 @@
|
|||||||
<md-filled-button type="submit">{{.i18n.next}}</md-filled-button>
|
<md-filled-button type="submit">{{.i18n.next}}</md-filled-button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.columns-two {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
</style>
|
@ -1,166 +0,0 @@
|
|||||||
<link rel="stylesheet" href="https://unpkg.com/tailwindcss@1.4.6/dist/base.min.css">
|
|
||||||
<link rel="stylesheet" href="https://unpkg.com/tailwindcss@1.4.6/dist/components.min.css">
|
|
||||||
<link rel="stylesheet" href="https://unpkg.com/@tailwindcss/typography@0.1.2/dist/typography.min.css">
|
|
||||||
<link rel="stylesheet" href="https://unpkg.com/tailwindcss@1.4.6/dist/utilities.min.css">
|
|
||||||
|
|
||||||
<div class="banner-container">
|
|
||||||
{{if gt (len .userinfo.Banner) 0}}
|
|
||||||
<img src="/api/avatar/{{.userinfo.Banner}}" alt="Banner" class="banner">
|
|
||||||
{{end}}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="left-part name-card">
|
|
||||||
{{if gt (len .userinfo.Avatar) 0}}
|
|
||||||
<img src="/api/avatar/{{.userinfo.Avatar}}" alt="Avatar" class="avatar">
|
|
||||||
{{else}}
|
|
||||||
<div class="avatar empty">
|
|
||||||
<span class="material-symbols-outlined">account_circle</span>
|
|
||||||
</div>
|
|
||||||
{{end}}
|
|
||||||
|
|
||||||
<div class="name">
|
|
||||||
<h2 class="username">{{.userinfo.Nick}}</h2>
|
|
||||||
<h6 class="nickname">@{{.userinfo.Name}}</h6>
|
|
||||||
</div>
|
|
||||||
{{if gt (len .userinfo.Description) 0}}
|
|
||||||
<div class="description">{{.userinfo.Description}}</div>
|
|
||||||
{{else}}
|
|
||||||
<div class="description empty">No description yet.</div>
|
|
||||||
{{end}}
|
|
||||||
<div class="uid">#{{.uid}}</div>
|
|
||||||
|
|
||||||
<div class="metadata">
|
|
||||||
<div><span class="material-symbols-outlined">calendar_month</span> {{.joined_at}}</div>
|
|
||||||
<div><span class="material-symbols-outlined">cake</span> {{.birthday_at}}</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="actions">
|
|
||||||
<md-filled-tonal-button class="action" href="/users/me/personalize">
|
|
||||||
Personalize
|
|
||||||
<span slot="icon" class="material-symbols-outlined">palette</span>
|
|
||||||
</md-filled-tonal-button>
|
|
||||||
<md-filled-tonal-button disabled class="action" href="/users/me/security">
|
|
||||||
Security
|
|
||||||
<span slot="icon" class="material-symbols-outlined">security</span>
|
|
||||||
</md-filled-tonal-button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="right-part">
|
|
||||||
<article class="personal-page prose">
|
|
||||||
{{.personal_page}}
|
|
||||||
</article>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.avatar {
|
|
||||||
display: block;
|
|
||||||
width: 64px;
|
|
||||||
height: 64px;
|
|
||||||
|
|
||||||
clip-path: circle();
|
|
||||||
}
|
|
||||||
|
|
||||||
.avatar.empty {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
background-color: var(--md-sys-color-secondary);
|
|
||||||
color: var(--md-sys-color-on-secondary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.banner-container {
|
|
||||||
grid-column: span 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
.banner {
|
|
||||||
display: block;
|
|
||||||
object-fit: cover;
|
|
||||||
border-radius: 28px;
|
|
||||||
height: 180px;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.name-card {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.name-card .name {
|
|
||||||
display: flex;
|
|
||||||
align-items: baseline;
|
|
||||||
gap: 0.3rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.name-card .username {
|
|
||||||
margin: 0;
|
|
||||||
font-size: 1.5rem;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
.name-card .nickname {
|
|
||||||
margin: 0;
|
|
||||||
font-size: 0.75rem;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
|
|
||||||
.name-card .uid {
|
|
||||||
margin-top: -0.8rem;
|
|
||||||
font-size: 0.7rem;
|
|
||||||
font-weight: 400;
|
|
||||||
font-family: Roboto Mono, monospace;
|
|
||||||
}
|
|
||||||
|
|
||||||
.name-card .description {
|
|
||||||
margin-top: -1.25rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.description.empty {
|
|
||||||
font-style: italic;
|
|
||||||
}
|
|
||||||
|
|
||||||
.name-card .metadata {
|
|
||||||
font-size: 0.85rem;
|
|
||||||
font-weight: 500;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
.metadata > div {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 0.25rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.metadata .material-symbols-outlined {
|
|
||||||
font-size: 1rem;
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.actions {
|
|
||||||
display: flex;
|
|
||||||
gap: 0.5rem;
|
|
||||||
margin: 0 -0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (min-width: 768px) {
|
|
||||||
.actions {
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.actions .action {
|
|
||||||
width: fit-content;
|
|
||||||
}
|
|
||||||
|
|
||||||
.actions .material-symbols-outlined {
|
|
||||||
font-size: 20px;
|
|
||||||
margin-bottom: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.left-part .prose {
|
|
||||||
min-width: 0;
|
|
||||||
max-width: unset;
|
|
||||||
}
|
|
||||||
</style>
|
|
3
pkg/views/users/me/index.gohtml
Normal file
3
pkg/views/users/me/index.gohtml
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
<h1>
|
||||||
|
You are accepted!
|
||||||
|
</h1>
|
@ -1,138 +0,0 @@
|
|||||||
<div class="left-part">
|
|
||||||
<img class="logo" alt="Logo" src="/favicon.png" width="64" height="64"/>
|
|
||||||
|
|
||||||
<h1 class="title">Personalize</h1>
|
|
||||||
<p class="caption">Personalize your account, and make us provide better service for you.</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="right-part">
|
|
||||||
<div class="responsive-title-gap"></div>
|
|
||||||
|
|
||||||
<div class="action-form">
|
|
||||||
<div class="input-label">Avatar</div>
|
|
||||||
<input
|
|
||||||
id="avatar-input"
|
|
||||||
class="block-field"
|
|
||||||
name="avatar"
|
|
||||||
type="file"
|
|
||||||
accept="image/*"
|
|
||||||
placeholder="Avatar"
|
|
||||||
>
|
|
||||||
|
|
||||||
<div class="input-label">Banner</div>
|
|
||||||
<input
|
|
||||||
id="banner-input"
|
|
||||||
class="block-field"
|
|
||||||
name="banner"
|
|
||||||
type="file"
|
|
||||||
accept="image/*"
|
|
||||||
placeholder="Banner"
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<hr class="divider"/>
|
|
||||||
|
|
||||||
<form class="action-form" action="/users/me/personalize" method="POST">
|
|
||||||
<div class="columns-two">
|
|
||||||
<md-outlined-text-field
|
|
||||||
class="block-field"
|
|
||||||
name="name"
|
|
||||||
type="text"
|
|
||||||
autocomplete="username"
|
|
||||||
label="Username"
|
|
||||||
disabled
|
|
||||||
value="{{.userinfo.Name}}"
|
|
||||||
>
|
|
||||||
</md-outlined-text-field>
|
|
||||||
|
|
||||||
<md-outlined-text-field
|
|
||||||
class="block-field"
|
|
||||||
name="nick"
|
|
||||||
type="text"
|
|
||||||
autocomplete="nickname"
|
|
||||||
label="Nickname"
|
|
||||||
value="{{.userinfo.Nick}}"
|
|
||||||
>
|
|
||||||
</md-outlined-text-field>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="columns-two">
|
|
||||||
<md-outlined-text-field
|
|
||||||
class="block-field"
|
|
||||||
name="first_name"
|
|
||||||
type="text"
|
|
||||||
autocomplete="given_name"
|
|
||||||
label="First Name"
|
|
||||||
value="{{if gt (len .userinfo.Profile.FirstName) 0}}{{.userinfo.Profile.FirstName}}{{end}}"
|
|
||||||
>
|
|
||||||
</md-outlined-text-field>
|
|
||||||
|
|
||||||
<md-outlined-text-field
|
|
||||||
class="block-field"
|
|
||||||
name="last_name"
|
|
||||||
type="text"
|
|
||||||
autocomplete="family_name"
|
|
||||||
label="Last Name"
|
|
||||||
value="{{if gt (len .userinfo.Profile.LastName) 0}}{{.userinfo.Profile.LastName}}{{end}}"
|
|
||||||
>
|
|
||||||
</md-outlined-text-field>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<md-outlined-text-field
|
|
||||||
class="block-field"
|
|
||||||
name="birthday"
|
|
||||||
type="date"
|
|
||||||
label="Birthday"
|
|
||||||
value="{{if ne .birthday nil}}{{.birthday}}{{end}}"
|
|
||||||
>
|
|
||||||
</md-outlined-text-field>
|
|
||||||
|
|
||||||
<md-outlined-text-field
|
|
||||||
class="block-field"
|
|
||||||
name="description"
|
|
||||||
type="textarea"
|
|
||||||
autocomplete="off"
|
|
||||||
label="Description"
|
|
||||||
value="{{if gt (len .userinfo.Description) 0}}{{.userinfo.Description}}{{end}}"
|
|
||||||
style="resize: vertical"
|
|
||||||
>
|
|
||||||
</md-outlined-text-field>
|
|
||||||
|
|
||||||
<div class="action-form-buttons">
|
|
||||||
<md-text-button type="button" href="/users/me">{{.i18n.back}}</md-text-button>
|
|
||||||
<md-filled-button type="submit">{{.i18n.apply}}</md-filled-button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.input-label {
|
|
||||||
font-size: 14px;
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
document.getElementById("avatar-input").addEventListener("input", (evt) => {
|
|
||||||
if (!evt.target.files) return
|
|
||||||
const data = new FormData();
|
|
||||||
data.set("avatar", evt.target.files[0])
|
|
||||||
fetch("/api/users/me/avatar", {
|
|
||||||
method: "PUT",
|
|
||||||
body: data,
|
|
||||||
}).then(() => {
|
|
||||||
location.href = "/users/me"
|
|
||||||
})
|
|
||||||
})
|
|
||||||
document.getElementById("banner-input").addEventListener("input", (evt) => {
|
|
||||||
if (!evt.target.files) return
|
|
||||||
const data = new FormData();
|
|
||||||
data.set("banner", evt.target.files[0])
|
|
||||||
fetch("/api/users/me/banner", {
|
|
||||||
method: "PUT",
|
|
||||||
body: data,
|
|
||||||
}).then(() => {
|
|
||||||
location.href = "/users/me"
|
|
||||||
})
|
|
||||||
})
|
|
||||||
</script>
|
|
Loading…
x
Reference in New Issue
Block a user