Use uni-token

💄 A lot of optimization
This commit is contained in:
LittleSheep 2024-02-21 22:58:51 +08:00
parent 4101043d65
commit 1e04f2029f
50 changed files with 319 additions and 490 deletions

9
go.mod
View File

@ -12,7 +12,7 @@ require (
github.com/rs/zerolog v1.31.0
github.com/samber/lo v1.39.0
github.com/spf13/viper v1.18.2
golang.org/x/crypto v0.18.0
golang.org/x/crypto v0.19.0
golang.org/x/oauth2 v0.16.0
gorm.io/datatypes v1.2.0
gorm.io/driver/postgres v1.5.4
@ -20,6 +20,7 @@ require (
)
require (
code.smartsheep.studio/hydrogen/identity v0.0.0-20240221130517-c169ffdacda8 // indirect
github.com/andybalholm/brotli v1.0.5 // indirect
github.com/fsnotify/fsnotify v1.7.0 // indirect
github.com/gabriel-vasile/mimetype v1.4.2 // indirect
@ -59,11 +60,13 @@ require (
github.com/valyala/tcplisten v1.0.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
golang.org/x/exp v0.0.0-20231219180239-dc181d75b848 // indirect
golang.org/x/net v0.20.0 // indirect
golang.org/x/net v0.21.0 // indirect
golang.org/x/sync v0.5.0 // indirect
golang.org/x/sys v0.16.0 // indirect
golang.org/x/sys v0.17.0 // indirect
golang.org/x/text v0.14.0 // indirect
google.golang.org/appengine v1.6.8 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240221002015-b0ce06bbee7c // indirect
google.golang.org/grpc v1.61.1 // indirect
google.golang.org/protobuf v1.32.0 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect

22
go.sum
View File

@ -1,3 +1,9 @@
code.smartsheep.studio/hydrogen/identity v0.0.0-20240220134615-3b0cbbb6c9ed h1:/3rRncEKlN1GYWjUSJF8bUkwnCkTFon2opa+tGUTwEQ=
code.smartsheep.studio/hydrogen/identity v0.0.0-20240220134615-3b0cbbb6c9ed/go.mod h1:db+/Y/fLPSOu1JlsCoXEYPD26644S0S3Bg/1XNLtlHQ=
code.smartsheep.studio/hydrogen/identity v0.0.0-20240221124039-3393f751a072 h1:T3pP/cWpfHoxA6VrhFPq0EcrDVnUVXtfwQSzM3jFRfo=
code.smartsheep.studio/hydrogen/identity v0.0.0-20240221124039-3393f751a072/go.mod h1:db+/Y/fLPSOu1JlsCoXEYPD26644S0S3Bg/1XNLtlHQ=
code.smartsheep.studio/hydrogen/identity v0.0.0-20240221130517-c169ffdacda8 h1:WBi14r+jomgixVDFa8pPecrQshhlyJKBT51VZNs+PBY=
code.smartsheep.studio/hydrogen/identity v0.0.0-20240221130517-c169ffdacda8/go.mod h1:db+/Y/fLPSOu1JlsCoXEYPD26644S0S3Bg/1XNLtlHQ=
github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs=
github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
@ -146,6 +152,8 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc=
golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo=
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
golang.org/x/exp v0.0.0-20231219180239-dc181d75b848 h1:+iq7lrkxmFNBM7xx+Rae2W6uyPfhPeDWD+n+JgppptE=
golang.org/x/exp v0.0.0-20231219180239-dc181d75b848/go.mod h1:iRJReGqOEeBhDZGkGbynYwcHlctCvnjTYIamk7uXpHI=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
@ -156,6 +164,8 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug
golang.org/x/net v0.3.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE=
golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo=
golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4=
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/oauth2 v0.16.0 h1:aDkGMBSYxElaoP81NpoUoz2oo2R2wHdZpGToUxfyQrQ=
golang.org/x/oauth2 v0.16.0/go.mod h1:hqZ+0LWXsiVoZpeld6jVt06P3adbS2Uu911W1SsJv2o=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@ -174,6 +184,8 @@ golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU=
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA=
@ -192,6 +204,16 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM=
google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds=
google.golang.org/genproto v0.0.0-20231106174013-bbf56f31fb17 h1:wpZ8pe2x1Q3f2KyT5f8oP/fa9rHAKgFPr/HZdNuS+PQ=
google.golang.org/genproto v0.0.0-20240213162025-012b6fc9bca9 h1:9+tzLLstTlPTRyJTh+ah5wIMsBW5c4tQwGTN3thOW9Y=
google.golang.org/genproto/googleapis/rpc v0.0.0-20231120223509-83a465c0220f h1:ultW7fxlIvee4HYrtnaRPon9HpEgFk5zYpmfMgtKB5I=
google.golang.org/genproto/googleapis/rpc v0.0.0-20231120223509-83a465c0220f/go.mod h1:L9KNLi232K1/xB6f7AlSX692koaRnKaWSR0stBki0Yc=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240221002015-b0ce06bbee7c h1:NUsgEN92SQQqzfA+YtqYNqYmB3DMMYLlIwUZAQFVFbo=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240221002015-b0ce06bbee7c/go.mod h1:H4O17MA/PE9BsGx3w+a+W2VOLLD1Qf7oJneAoU6WktY=
google.golang.org/grpc v1.59.0 h1:Z5Iec2pjwb+LEOqzpB2MR12/eKFhDPhuqW91O+4bwUk=
google.golang.org/grpc v1.59.0/go.mod h1:aUPDwccQo6OTjy7Hct4AfBPD1GptF4fyUjIkQ9YtF98=
google.golang.org/grpc v1.61.1 h1:kLAiWrZs7YeDM6MumDe7m3y4aM6wacLzM1Y/wiLP9XY=
google.golang.org/grpc v1.61.1/go.mod h1:VUbo7IFqmF1QtCAstipjG0GIoq49KvMe9+h1jFLBNJs=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I=

View File

@ -1,6 +1,7 @@
package main
import (
"code.smartsheep.studio/hydrogen/interactive/pkg/grpc"
"code.smartsheep.studio/hydrogen/interactive/pkg/server"
"os"
"os/signal"
@ -37,6 +38,13 @@ func main() {
log.Fatal().Err(err).Msg("An error occurred when running database auto migration.")
}
// Connect other services
go func() {
if err := grpc.ConnectPassport(); err != nil {
log.Fatal().Err(err).Msg("An error occurred when connecting to identity grpc endpoint...")
}
}()
// Server
server.NewServer()
go server.Listen()

24
pkg/grpc/client.go Normal file
View File

@ -0,0 +1,24 @@
package grpc
import (
pwpb "code.smartsheep.studio/hydrogen/identity/pkg/grpc/proto"
"google.golang.org/grpc/credentials/insecure"
"github.com/spf13/viper"
"google.golang.org/grpc"
)
var Notify pwpb.NotifyClient
var Auth pwpb.AuthClient
func ConnectPassport() error {
addr := viper.GetString("identity.grpc_endpoint")
if conn, err := grpc.Dial(addr, grpc.WithTransportCredentials(insecure.NewCredentials())); err != nil {
return err
} else {
Notify = pwpb.NewNotifyClient(conn)
Auth = pwpb.NewAuthClient(conn)
}
return nil
}

View File

@ -2,6 +2,7 @@ package security
import (
"fmt"
"github.com/gofiber/fiber/v2"
"time"
"github.com/golang-jwt/jwt/v5"
@ -19,6 +20,11 @@ const (
JwtRefreshType = "refresh"
)
const (
CookieAccessKey = "identity_auth_key"
CookieRefreshKey = "identity_refresh_key"
)
func EncodeJwt(id string, typ, sub string, aud []string, exp time.Time) (string, error) {
tk := jwt.NewWithClaims(jwt.SigningMethodHS512, PayloadClaims{
jwt.RegisteredClaims{
@ -54,3 +60,22 @@ func DecodeJwt(str string) (PayloadClaims, error) {
return claims, fmt.Errorf("unexpected token payload: not payload claims type")
}
}
func SetJwtCookieSet(c *fiber.Ctx, access, refresh string) {
c.Cookie(&fiber.Cookie{
Name: CookieAccessKey,
Value: access,
Domain: viper.GetString("security.cookie_domain"),
SameSite: viper.GetString("security.cookie_samesite"),
Expires: time.Now().Add(60 * time.Minute),
Path: "/",
})
c.Cookie(&fiber.Cookie{
Name: CookieRefreshKey,
Value: refresh,
Domain: viper.GetString("security.cookie_domain"),
SameSite: viper.GetString("security.cookie_samesite"),
Expires: time.Now().Add(24 * 30 * time.Hour),
Path: "/",
})
}

View File

@ -1,35 +1,51 @@
package server
import (
"code.smartsheep.studio/hydrogen/interactive/pkg/database"
"code.smartsheep.studio/hydrogen/interactive/pkg/models"
"code.smartsheep.studio/hydrogen/interactive/pkg/security"
"code.smartsheep.studio/hydrogen/interactive/pkg/services"
"github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/middleware/keyauth"
"strconv"
"strings"
)
var auth = keyauth.New(keyauth.Config{
KeyLookup: "header:Authorization",
AuthScheme: "Bearer",
Validator: func(c *fiber.Ctx, token string) (bool, error) {
claims, err := security.DecodeJwt(token)
if err != nil {
return false, err
func authMiddleware(c *fiber.Ctx) error {
var token string
if cookie := c.Cookies(security.CookieAccessKey); len(cookie) > 0 {
token = cookie
}
if header := c.Get(fiber.HeaderAuthorization); len(header) > 0 {
tk := strings.Replace(header, "Bearer", "", 1)
token = strings.TrimSpace(tk)
}
c.Locals("token", token)
if err := authFunc(c); err != nil {
return err
}
return c.Next()
}
func authFunc(c *fiber.Ctx, overrides ...string) error {
var token string
if len(overrides) > 0 {
token = overrides[0]
} else {
if tk, ok := c.Locals("token").(string); !ok {
return fiber.NewError(fiber.StatusUnauthorized)
} else {
token = tk
}
}
id, _ := strconv.Atoi(claims.Subject)
var user models.Account
if err := database.C.Where(&models.Account{
BaseModel: models.BaseModel{ID: uint(id)},
}).First(&user).Error; err != nil {
return false, err
rtk := c.Cookies(security.CookieRefreshKey)
if user, atk, rtk, err := services.Authenticate(token, rtk); err == nil {
if atk != token {
security.SetJwtCookieSet(c, atk, rtk)
}
c.Locals("principal", user)
return true, nil
},
ContextKey: "token",
})
return nil
} else {
return err
}
}

View File

@ -1,93 +0,0 @@
package server
import (
"code.smartsheep.studio/hydrogen/interactive/pkg/services"
"context"
"encoding/json"
"fmt"
"github.com/gofiber/fiber/v2"
"github.com/google/uuid"
"github.com/spf13/viper"
"golang.org/x/oauth2"
)
var cfg oauth2.Config
func buildOauth2Config() {
cfg = oauth2.Config{
RedirectURL: fmt.Sprintf("https://%s/auth/callback", viper.GetString("domain")),
ClientID: viper.GetString("identity.client_id"),
ClientSecret: viper.GetString("identity.client_secret"),
Scopes: []string{"openid"},
Endpoint: oauth2.Endpoint{
AuthURL: fmt.Sprintf("%s/auth/o/connect", viper.GetString("identity.endpoint")),
TokenURL: fmt.Sprintf("%s/api/auth/token", viper.GetString("identity.endpoint")),
AuthStyle: oauth2.AuthStyleInParams,
},
}
}
func doLogin(c *fiber.Ctx) error {
buildOauth2Config()
url := cfg.AuthCodeURL(uuid.NewString())
return c.JSON(fiber.Map{
"target": url,
})
}
func postLogin(c *fiber.Ctx) error {
buildOauth2Config()
code := c.Query("code")
token, err := cfg.Exchange(context.Background(), code)
if err != nil {
return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("failed to exchange token: %q", err))
}
agent := fiber.
Get(fmt.Sprintf("%s/api/users/me", viper.GetString("identity.endpoint"))).
Set(fiber.HeaderAuthorization, fmt.Sprintf("Bearer %s", token.AccessToken))
_, body, errs := agent.Bytes()
if len(errs) > 0 {
return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("failed to get userinfo: %q", errs))
}
var userinfo services.IdentityUserinfo
err = json.Unmarshal(body, &userinfo)
if err != nil {
return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("failed to parse userinfo: %q", err))
}
account, err := services.LinkAccount(userinfo)
access, refresh, err := services.GetToken(account)
if err != nil {
return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("failed to get token: %q", err))
}
return c.JSON(fiber.Map{
"access_token": access,
"refresh_token": refresh,
})
}
func doRefreshToken(c *fiber.Ctx) error {
var data struct {
RefreshToken string `json:"refresh_token" validate:"required"`
}
if err := BindAndValidate(c, &data); err != nil {
return err
}
access, refresh, err := services.RefreshToken(data.RefreshToken)
if err != nil {
return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("failed to get token: %q", err))
}
return c.JSON(fiber.Map{
"access_token": access,
"refresh_token": refresh,
})
}

View File

@ -58,45 +58,41 @@ func NewServer() {
api := A.Group("/api").Name("API")
{
api.Get("/auth", doLogin)
api.Get("/auth/callback", postLogin)
api.Post("/auth/refresh", doRefreshToken)
api.Get("/users/me", auth, getUserinfo)
api.Get("/users/me", authMiddleware, getUserinfo)
api.Get("/users/:accountId", getOthersInfo)
api.Get("/users/:accountId/follow", auth, getAccountFollowed)
api.Post("/users/:accountId/follow", auth, doFollowAccount)
api.Get("/users/:accountId/follow", authMiddleware, getAccountFollowed)
api.Post("/users/:accountId/follow", authMiddleware, doFollowAccount)
api.Get("/attachments/o/:fileId", cache.New(cache.Config{
Expiration: 365 * 24 * time.Hour,
CacheControl: true,
}), openAttachment)
api.Post("/attachments", auth, uploadAttachment)
api.Post("/attachments", authMiddleware, uploadAttachment)
api.Get("/posts", listPost)
api.Get("/posts/:postId", getPost)
api.Post("/posts", auth, createPost)
api.Post("/posts/:postId/react/:reactType", auth, reactPost)
api.Put("/posts/:postId", auth, editPost)
api.Delete("/posts/:postId", auth, deletePost)
api.Post("/posts", authMiddleware, createPost)
api.Post("/posts/:postId/react/:reactType", authMiddleware, reactPost)
api.Put("/posts/:postId", authMiddleware, editPost)
api.Delete("/posts/:postId", authMiddleware, deletePost)
api.Get("/categories", listCategroies)
api.Post("/categories", auth, newCategory)
api.Put("/categories/:categoryId", auth, editCategory)
api.Delete("/categories/:categoryId", auth, deleteCategory)
api.Post("/categories", authMiddleware, newCategory)
api.Put("/categories/:categoryId", authMiddleware, editCategory)
api.Delete("/categories/:categoryId", authMiddleware, deleteCategory)
api.Get("/creators/posts", auth, listOwnPost)
api.Get("/creators/posts/:postId", auth, getOwnPost)
api.Get("/creators/posts", authMiddleware, listOwnPost)
api.Get("/creators/posts/:postId", authMiddleware, getOwnPost)
api.Get("/realms", listRealm)
api.Get("/realms/me", auth, listOwnedRealm)
api.Get("/realms/me/available", auth, listAvailableRealm)
api.Get("/realms/me", authMiddleware, listOwnedRealm)
api.Get("/realms/me/available", authMiddleware, listAvailableRealm)
api.Get("/realms/:realmId", getRealm)
api.Post("/realms", auth, createRealm)
api.Post("/realms/:realmId/invite", auth, inviteRealm)
api.Post("/realms/:realmId/kick", auth, kickRealm)
api.Put("/realms/:realmId", auth, editRealm)
api.Delete("/realms/:realmId", auth, deleteRealm)
api.Post("/realms", authMiddleware, createRealm)
api.Post("/realms/:realmId/invite", authMiddleware, inviteRealm)
api.Post("/realms/:realmId/kick", authMiddleware, kickRealm)
api.Put("/realms/:realmId", authMiddleware, editRealm)
api.Delete("/realms/:realmId", authMiddleware, deleteRealm)
}
A.Use("/", cache.New(cache.Config{

View File

@ -9,5 +9,8 @@ func getMetadata(c *fiber.Ctx) error {
return c.JSON(fiber.Map{
"name": viper.GetString("name"),
"domain": viper.GetString("domain"),
"components": fiber.Map{
"identity": viper.GetString("identity.endpoint"),
},
})
}

View File

@ -1,11 +1,13 @@
package services
import (
"code.smartsheep.studio/hydrogen/identity/pkg/grpc/proto"
"code.smartsheep.studio/hydrogen/interactive/pkg/database"
"code.smartsheep.studio/hydrogen/interactive/pkg/grpc"
"code.smartsheep.studio/hydrogen/interactive/pkg/models"
"fmt"
"github.com/gofiber/fiber/v2"
"context"
"github.com/spf13/viper"
"time"
)
func FollowAccount(followerId, followingId uint) error {
@ -32,22 +34,19 @@ func GetAccountFollowed(user models.Account, target models.Account) (models.Acco
return relationship, err == nil
}
func NotifyAccount(user models.Account, subject, content string, links ...fiber.Map) error {
agent := fiber.Post(viper.GetString("identity.endpoint") + "/api/dev/notify")
agent.JSON(fiber.Map{
"client_id": viper.GetString("identity.client_id"),
"client_secret": viper.GetString("identity.client_secret"),
"subject": subject,
"content": content,
"links": links,
"user_id": user.ExternalID,
func NotifyAccount(user models.Account, subject, content string, links ...*proto.NotifyLink) error {
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
defer cancel()
_, err := grpc.Notify.NotifyUser(ctx, &proto.NotifyRequest{
ClientId: viper.GetString("identity.client_id"),
ClientSecret: viper.GetString("identity.client_secret"),
Subject: subject,
Content: content,
Links: links,
RecipientId: uint64(user.ID),
IsImportant: false,
})
if status, body, errs := agent.Bytes(); len(errs) > 0 {
return errs[0]
} else if status != 200 {
return fmt.Errorf(string(body))
}
return nil
return err
}

View File

@ -1,40 +1,29 @@
package services
import (
"code.smartsheep.studio/hydrogen/identity/pkg/grpc/proto"
"code.smartsheep.studio/hydrogen/interactive/pkg/database"
"code.smartsheep.studio/hydrogen/interactive/pkg/grpc"
"code.smartsheep.studio/hydrogen/interactive/pkg/models"
"code.smartsheep.studio/hydrogen/interactive/pkg/security"
"context"
"errors"
"fmt"
"github.com/google/uuid"
"gorm.io/gorm"
"strconv"
"time"
)
type IdentityUserinfo struct {
Sub string `json:"sub"`
Name string `json:"name"`
Email string `json:"email"`
Picture string `json:"picture"`
PreferredUsername string `json:"preferred_username"`
}
func LinkAccount(userinfo IdentityUserinfo) (models.Account, error) {
id, _ := strconv.Atoi(userinfo.Sub)
func LinkAccount(userinfo *proto.Userinfo) (models.Account, error) {
var account models.Account
if err := database.C.Where(&models.Account{
ExternalID: uint(id),
ExternalID: uint(userinfo.Id),
}).First(&account).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
account = models.Account{
Name: userinfo.Name,
Nick: userinfo.PreferredUsername,
Avatar: userinfo.Picture,
Nick: userinfo.Nick,
Avatar: userinfo.Avatar,
EmailAddress: userinfo.Email,
PowerLevel: 0,
ExternalID: uint(id),
ExternalID: uint(userinfo.Id),
}
return account, database.C.Save(&account).Error
}
@ -42,8 +31,8 @@ func LinkAccount(userinfo IdentityUserinfo) (models.Account, error) {
}
account.Name = userinfo.Name
account.Nick = userinfo.PreferredUsername
account.Avatar = userinfo.Picture
account.Nick = userinfo.Nick
account.Avatar = userinfo.Avatar
account.EmailAddress = userinfo.Email
err := database.C.Save(&account).Error
@ -51,51 +40,21 @@ func LinkAccount(userinfo IdentityUserinfo) (models.Account, error) {
return account, err
}
func GetToken(account models.Account) (string, string, error) {
func Authenticate(atk, rtk string) (models.Account, string, string, error) {
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
defer cancel()
var err error
var refresh, access string
sub := strconv.Itoa(int(account.ID))
access, err = security.EncodeJwt(
uuid.NewString(),
security.JwtAccessType,
sub,
[]string{"interactive"},
time.Now().Add(30*time.Minute),
)
var user models.Account
reply, err := grpc.Auth.Authenticate(ctx, &proto.AuthRequest{
AccessToken: atk,
RefreshToken: &rtk,
})
if err != nil {
return refresh, access, err
}
refresh, err = security.EncodeJwt(
uuid.NewString(),
security.JwtRefreshType,
sub,
[]string{"interactive"},
time.Now().Add(30*24*time.Hour),
)
if err != nil {
return refresh, access, err
return user, reply.GetAccessToken(), reply.GetRefreshToken(), err
}
return access, refresh, nil
}
func RefreshToken(token string) (string, string, error) {
parseInt := func(str string) int {
val, _ := strconv.Atoi(str)
return val
}
var account models.Account
if claims, err := security.DecodeJwt(token); err != nil {
return "404", "403", err
} else if claims.Type != security.JwtRefreshType {
return "404", "403", fmt.Errorf("invalid token type, expected refresh token")
} else if err := database.C.Where(models.Account{
BaseModel: models.BaseModel{ID: uint(parseInt(claims.Subject))},
}).First(&account).Error; err != nil {
return "404", "403", err
}
return GetToken(account)
user, err = LinkAccount(reply.Userinfo)
return user, reply.GetAccessToken(), reply.GetRefreshToken(), err
}

View File

@ -1,11 +1,11 @@
package services
import (
"code.smartsheep.studio/hydrogen/identity/pkg/grpc/proto"
"errors"
"fmt"
"time"
"github.com/gofiber/fiber/v2"
"github.com/rs/zerolog/log"
"code.smartsheep.studio/hydrogen/interactive/pkg/database"
@ -230,7 +230,7 @@ func NewPost(
op.Author,
fmt.Sprintf("%s replied you", user.Name),
fmt.Sprintf("%s replied your post. Check it out!", user.Name),
fiber.Map{"label": "Related post", "url": postUrl},
&proto.NotifyLink{Label: "Related post", Url: postUrl},
)
if err != nil {
log.Error().Err(err).Msg("An error occurred when notifying user...")
@ -257,7 +257,7 @@ func NewPost(
account,
fmt.Sprintf("%s just posted a post", user.Name),
"Account you followed post a brand new post. Check it out!",
fiber.Map{"label": "Related post", "url": postUrl},
&proto.NotifyLink{Label: "Related post", Url: postUrl},
)
if err != nil {
log.Error().Err(err).Msg("An error occurred when notifying user...")

View File

@ -1,5 +1,5 @@
:root {
--bs-body-font-family: "IBM Plex Serif", "Noto Serif SC", sans-serif !important;
--bs-body-font-family: "IBM Plex Sans", "Noto Serif SC", sans-serif !important;
}
html,
@ -7,130 +7,117 @@ body {
font-family: var(--bs-body-font-family);
}
/* ibm-plex-serif-100 - latin */
/* ibm-plex-sans-100 - latin */
@font-face {
font-display: swap; /* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */
font-family: "IBM Plex Serif";
font-family: 'IBM Plex Sans';
font-style: normal;
font-weight: 100;
src: url("./ibm-plex-serif-v19-latin-100.woff2") format("woff2"); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
src: url('./ibm-plex-sans-v19-latin-100.woff2') format('woff2'); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
}
/* ibm-plex-serif-100italic - latin */
/* ibm-plex-sans-100italic - latin */
@font-face {
font-display: swap; /* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */
font-family: "IBM Plex Serif";
font-family: 'IBM Plex Sans';
font-style: italic;
font-weight: 100;
src: url("./ibm-plex-serif-v19-latin-100italic.woff2") format("woff2"); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
src: url('./ibm-plex-sans-v19-latin-100italic.woff2') format('woff2'); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
}
/* ibm-plex-serif-200 - latin */
/* ibm-plex-sans-200 - latin */
@font-face {
font-display: swap; /* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */
font-family: "IBM Plex Serif";
font-family: 'IBM Plex Sans';
font-style: normal;
font-weight: 200;
src: url("./ibm-plex-serif-v19-latin-200.woff2") format("woff2"); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
src: url('./ibm-plex-sans-v19-latin-200.woff2') format('woff2'); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
}
/* ibm-plex-serif-200italic - latin */
/* ibm-plex-sans-200italic - latin */
@font-face {
font-display: swap; /* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */
font-family: "IBM Plex Serif";
font-family: 'IBM Plex Sans';
font-style: italic;
font-weight: 200;
src: url("./ibm-plex-serif-v19-latin-200italic.woff2") format("woff2"); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
src: url('./ibm-plex-sans-v19-latin-200italic.woff2') format('woff2'); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
}
/* ibm-plex-serif-300 - latin */
/* ibm-plex-sans-300 - latin */
@font-face {
font-display: swap; /* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */
font-family: "IBM Plex Serif";
font-family: 'IBM Plex Sans';
font-style: normal;
font-weight: 300;
src: url("./ibm-plex-serif-v19-latin-300.woff2") format("woff2"); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
src: url('./ibm-plex-sans-v19-latin-300.woff2') format('woff2'); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
}
/* ibm-plex-serif-300italic - latin */
/* ibm-plex-sans-300italic - latin */
@font-face {
font-display: swap; /* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */
font-family: "IBM Plex Serif";
font-family: 'IBM Plex Sans';
font-style: italic;
font-weight: 300;
src: url("./ibm-plex-serif-v19-latin-300italic.woff2") format("woff2"); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
src: url('./ibm-plex-sans-v19-latin-300italic.woff2') format('woff2'); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
}
/* ibm-plex-serif-regular - latin */
/* ibm-plex-sans-regular - latin */
@font-face {
font-display: swap; /* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */
font-family: "IBM Plex Serif";
font-family: 'IBM Plex Sans';
font-style: normal;
font-weight: 400;
src: url("./ibm-plex-serif-v19-latin-regular.woff2") format("woff2"); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
src: url('./ibm-plex-sans-v19-latin-regular.woff2') format('woff2'); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
}
/* ibm-plex-serif-italic - latin */
/* ibm-plex-sans-italic - latin */
@font-face {
font-display: swap; /* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */
font-family: "IBM Plex Serif";
font-family: 'IBM Plex Sans';
font-style: italic;
font-weight: 400;
src: url("./ibm-plex-serif-v19-latin-italic.woff2") format("woff2"); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
src: url('./ibm-plex-sans-v19-latin-italic.woff2') format('woff2'); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
}
/* ibm-plex-serif-500 - latin */
/* ibm-plex-sans-500 - latin */
@font-face {
font-display: swap; /* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */
font-family: "IBM Plex Serif";
font-family: 'IBM Plex Sans';
font-style: normal;
font-weight: 500;
src: url("./ibm-plex-serif-v19-latin-500.woff2") format("woff2"); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
src: url('./ibm-plex-sans-v19-latin-500.woff2') format('woff2'); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
}
/* ibm-plex-serif-500italic - latin */
/* ibm-plex-sans-500italic - latin */
@font-face {
font-display: swap; /* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */
font-family: "IBM Plex Serif";
font-family: 'IBM Plex Sans';
font-style: italic;
font-weight: 500;
src: url("./ibm-plex-serif-v19-latin-500italic.woff2") format("woff2"); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
src: url('./ibm-plex-sans-v19-latin-500italic.woff2') format('woff2'); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
}
/* ibm-plex-serif-600 - latin */
/* ibm-plex-sans-600 - latin */
@font-face {
font-display: swap; /* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */
font-family: "IBM Plex Serif";
font-family: 'IBM Plex Sans';
font-style: normal;
font-weight: 600;
src: url("./ibm-plex-serif-v19-latin-600.woff2") format("woff2"); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
src: url('./ibm-plex-sans-v19-latin-600.woff2') format('woff2'); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
}
/* ibm-plex-serif-600italic - latin */
/* ibm-plex-sans-600italic - latin */
@font-face {
font-display: swap; /* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */
font-family: "IBM Plex Serif";
font-family: 'IBM Plex Sans';
font-style: italic;
font-weight: 600;
src: url("./ibm-plex-serif-v19-latin-600italic.woff2") format("woff2"); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
src: url('./ibm-plex-sans-v19-latin-600italic.woff2') format('woff2'); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
}
/* ibm-plex-serif-700 - latin */
/* ibm-plex-sans-700 - latin */
@font-face {
font-display: swap; /* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */
font-family: "IBM Plex Serif";
font-family: 'IBM Plex Sans';
font-style: normal;
font-weight: 700;
src: url("./ibm-plex-serif-v19-latin-700.woff2") format("woff2"); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
src: url('./ibm-plex-sans-v19-latin-700.woff2') format('woff2'); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
}
/* ibm-plex-serif-700italic - latin */
/* ibm-plex-sans-700italic - latin */
@font-face {
font-display: swap; /* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */
font-family: "IBM Plex Serif";
font-family: 'IBM Plex Sans';
font-style: italic;
font-weight: 700;
src: url("./ibm-plex-serif-v19-latin-700italic.woff2") format("woff2"); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
src: url('./ibm-plex-sans-v19-latin-700italic.woff2') format('woff2'); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
}
/* noto-serif-sc-200 - chinese-simplified */

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,22 @@
import { Show } from "solid-js";
export default function Avatar(props: { user: any }) {
return (
<Show
when={props.user?.avatar}
fallback={
<div class="avatar placeholder">
<div class="w-12 h-12 bg-neutral text-neutral-content">
<span class="text-xl uppercase">{props.user?.name?.substring(0, 1)}</span>
</div>
</div>
}
>
<div class="avatar">
<div class="w-12">
<img alt="avatar" src={props.user?.avatar} />
</div>
</div>
</Show>
);
}

View File

@ -4,6 +4,7 @@ import { request } from "../../scripts/request.ts";
import PostAttachments from "./PostAttachments.tsx";
import * as marked from "marked";
import DOMPurify from "dompurify";
import Avatar from "../Avatar.tsx";
export default function PostItem(props: {
post: any;
@ -28,7 +29,7 @@ export default function PostItem(props: {
setReacting(true);
const res = await request(`/api/posts/${item.id}/react/${type}`, {
method: "POST",
headers: { Authorization: `Bearer ${getAtk()}` },
headers: { Authorization: `Bearer ${getAtk()}` }
});
if (res.status !== 201 && res.status !== 204) {
props.onError(await res.text());
@ -46,15 +47,8 @@ export default function PostItem(props: {
<Show when={!props.noAuthor}>
<a href={`/accounts/${props.post.author.name}`}>
<div class="flex bg-base-200">
<div class="avatar pl-[20px]">
<div class="w-12">
<Show
when={props.post.author.avatar}
fallback={<span class="text-3xl">{props.post.author.name.substring(0, 1)}</span>}
>
<img alt="avatar" src={props.post.author.avatar} />
</Show>
</div>
<div class="pl-[20px]">
<Avatar user={props.post.author} />
</div>
<div class="flex items-center px-5">
<div>
@ -122,7 +116,8 @@ export default function PostItem(props: {
<Show when={!props.noControl}>
<div class="relative">
<Show when={!userinfo?.isLoggedIn}>
<div class="px-7 py-2.5 h-12 w-full opacity-0 transition-opacity hover:opacity-100 bg-base-100 border-t border-base-200 z-[1] absolute top-0 left-0">
<div
class="px-7 py-2.5 h-12 w-full opacity-0 transition-opacity hover:opacity-100 bg-base-100 border-t border-base-200 z-[1] absolute top-0 left-0">
<b>Login!</b> To access entire platform.
</div>
</Show>

View File

@ -4,6 +4,7 @@ import { request } from "../../scripts/request.ts";
import styles from "./PostPublish.module.css";
import PostEditActions from "./PostEditActions.tsx";
import Avatar from "../Avatar.tsx";
export default function PostPublish(props: {
replying?: any,
@ -100,7 +101,7 @@ export default function PostPublish(props: {
categories: categories(),
tags: tags(),
realm_id: props.realmId,
published_at: publishedAt() ? new Date(publishedAt()) : new Date(),
published_at: publishedAt() ? new Date(publishedAt()) : new Date()
})
});
if (res.status !== 200) {
@ -124,13 +125,8 @@ export default function PostPublish(props: {
<>
<form id="publish" onSubmit={(evt) => (props.editing ? doEdit : doPost)(evt)} onReset={() => resetForm()}>
<div id="publish-identity" class="flex border-y border-base-200">
<div class="avatar pl-[20px]">
<div class="w-12">
<Show when={userinfo?.profiles?.avatar}
fallback={<span class="text-3xl">{userinfo?.displayName.substring(0, 1)}</span>}>
<img alt="avatar" src={userinfo?.profiles?.avatar} />
</Show>
</div>
<div class="pl-[20px]">
<Avatar user={userinfo?.profiles} />
</div>
<div class="flex flex-grow">
<input name="title" value={props.editing?.title ?? ""}

View File

@ -37,8 +37,6 @@ const router = (basename?: string) => (
<Route path="/publish" component={lazy(() => import("./pages/creators/publish.tsx"))} />
<Route path="/edit/:postId" component={lazy(() => import("./pages/creators/edit.tsx"))} />
</Route>
<Route path="/auth" component={lazy(() => import("./pages/auth/callout.tsx"))} />
<Route path="/auth/callback" component={lazy(() => import("./pages/auth/callback.tsx"))} />
</Router>
</UserinfoProvider>
</WellKnownProvider>

View File

@ -1,4 +1,4 @@
import { For, Match, Switch } from "solid-js";
import { createMemo, For, Match, Switch } from "solid-js";
import { clearUserinfo, useUserinfo } from "../../stores/userinfo.tsx";
import { useNavigate } from "@solidjs/router";
import { useWellKnown } from "../../stores/wellKnown.tsx";
@ -20,9 +20,11 @@ export default function Navigator() {
const userinfo = useUserinfo();
const navigate = useNavigate();
const endpoint = createMemo(() => wellKnown?.components?.identity)
function logout() {
clearUserinfo();
navigate("/auth/login");
navigate("/");
}
return (
@ -54,7 +56,7 @@ export default function Navigator() {
</button>
</Match>
<Match when={!userinfo?.isLoggedIn}>
<a href="/auth" class="btn btn-sm btn-primary">
<a href={`${endpoint()}/auth/login?redirect_uri=${window.location}`} class="btn btn-sm btn-primary">
Login
</a>
</Match>

View File

@ -1,65 +0,0 @@
import { createSignal, Show } from "solid-js";
import { readProfiles } from "../../stores/userinfo.tsx";
import { useNavigate } from "@solidjs/router";
import Cookie from "universal-cookie";
import { request } from "../../scripts/request.ts";
export default function AuthCallback() {
const [error, setError] = createSignal<string | null>(null);
const [status, setStatus] = createSignal("Communicating with Goatpass...");
const navigate = useNavigate();
async function callback() {
const res = await request(`/api/auth/callback${location.search}`);
if (res.status !== 200) {
setError(await res.text());
} else {
const data = await res.json();
new Cookie().set("access_token", data["access_token"], { path: "/", maxAge: undefined });
new Cookie().set("refresh_token", data["refresh_token"], { path: "/", maxAge: undefined });
setStatus("Pulling your personal data...");
await readProfiles();
setStatus("Redirecting...")
setTimeout(() => navigate("/"), 1850)
}
}
callback();
return (
<div class="w-full h-full flex justify-center items-center">
<div class="card w-[480px] max-w-screen shadow-xl">
<div class="card-body">
<div id="header" class="text-center mb-5">
<h1 class="text-xl font-bold">Authenticate</h1>
<p>Via your Goatpass account</p>
</div>
<div class="pt-16 text-center">
<div class="text-center">
<div>
<span class="loading loading-lg loading-bars"></span>
</div>
<span>{status()}</span>
</div>
</div>
<Show when={error()} fallback={<div class="mt-16"></div>}>
<div id="alerts" class="mt-16">
<div role="alert" class="alert alert-error">
<svg xmlns="http://www.w3.org/2000/svg" class="stroke-current shrink-0 h-6 w-6"
fill="none"
viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
<span class="capitalize">{error()}</span>
</div>
</div>
</Show>
</div>
</div>
</div>
);
}

View File

@ -1,56 +0,0 @@
import { createSignal, Show } from "solid-js";
import { request } from "../../scripts/request.ts";
export default function AuthCallout() {
const [error, setError] = createSignal<string | null>(null);
const [status, setStatus] = createSignal("Communicating with Goatpass...");
async function communicate() {
const res = await request(`/api/auth${location.search}`);
if (res.status !== 200) {
setError(await res.text());
} else {
const data = await res.json();
setStatus("Got you! Now redirecting...");
window.open(data["target"], "_self");
}
}
communicate();
return (
<div class="w-full h-full flex justify-center items-center">
<div class="card w-[480px] max-w-screen shadow-xl">
<div class="card-body">
<div id="header" class="text-center mb-5">
<h1 class="text-xl font-bold">Authenticate</h1>
<p>Via your Goatpass account</p>
</div>
<div class="pt-16 text-center">
<div class="text-center">
<div>
<span class="loading loading-lg loading-bars"></span>
</div>
<span>{status()}</span>
</div>
</div>
<Show when={error()} fallback={<div class="mt-16"></div>}>
<div id="alerts" class="mt-16">
<div role="alert" class="alert alert-error">
<svg xmlns="http://www.w3.org/2000/svg" class="stroke-current shrink-0 h-6 w-6"
fill="none"
viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
<span class="capitalize">{error()}</span>
</div>
</div>
</Show>
</div>
</div>
</div>
);
}

View File

@ -1,92 +1,73 @@
import Cookie from "universal-cookie";
import {createContext, useContext} from "solid-js";
import {createStore} from "solid-js/store";
import { createContext, useContext } from "solid-js";
import { createStore } from "solid-js/store";
import { request } from "../scripts/request.ts";
export interface Userinfo {
isLoggedIn: boolean,
displayName: string,
profiles: any,
isLoggedIn: boolean,
displayName: string,
profiles: any,
}
const UserinfoContext = createContext<Userinfo>();
const defaultUserinfo: Userinfo = {
isLoggedIn: false,
displayName: "Citizen",
profiles: null,
isLoggedIn: false,
displayName: "Citizen",
profiles: null
};
const [userinfo, setUserinfo] = createStore<Userinfo>(structuredClone(defaultUserinfo));
export function getAtk(): string {
return new Cookie().get("access_token");
}
export async function refreshAtk() {
const rtk = new Cookie().get("refresh_token");
const res = await request("/api/auth/refresh", {
method: "POST",
headers: {"Content-Type": "application/json"},
body: JSON.stringify({
refresh_token: rtk,
})
});
if (res.status !== 200) {
console.error(await res.text())
} else {
const data = await res.json();
new Cookie().set("access_token", data["access_token"], {path: "/", maxAge: undefined});
new Cookie().set("refresh_token", data["refresh_token"], {path: "/", maxAge: undefined});
}
return new Cookie().get("identity_auth_key");
}
function checkLoggedIn(): boolean {
return new Cookie().get("access_token");
return new Cookie().get("identity_auth_key");
}
export async function readProfiles(recovering = true) {
if (!checkLoggedIn()) return;
export async function readProfiles() {
if (!checkLoggedIn()) return;
const res = await request("/api/users/me", {
headers: {"Authorization": `Bearer ${getAtk()}`}
});
const res = await request("/api/users/me", {
headers: { "Authorization": `Bearer ${getAtk()}` }
});
if (res.status !== 200) {
if (recovering) {
// Auto retry after refresh access token
await refreshAtk();
return await readProfiles(false);
} else {
clearUserinfo();
window.location.reload();
}
}
if (res.status !== 200) {
clearUserinfo();
window.location.reload();
}
const data = await res.json();
const data = await res.json();
setUserinfo({
isLoggedIn: true,
displayName: data["name"],
profiles: data,
});
setUserinfo({
isLoggedIn: true,
displayName: data["name"],
profiles: data
});
}
export function clearUserinfo() {
new Cookie().remove("access_token", {path: "/", maxAge: undefined});
new Cookie().remove("refresh_token", {path: "/", maxAge: undefined});
setUserinfo(defaultUserinfo);
const cookies = document.cookie.split(";");
for (let i = 0; i < cookies.length; i++) {
const cookie = cookies[i];
const eqPos = cookie.indexOf("=");
const name = eqPos > -1 ? cookie.substring(0, eqPos) : cookie;
document.cookie = name + "=;expires=Thu, 01 Jan 1970 00:00:00 GMT";
}
setUserinfo(defaultUserinfo);
}
export function UserinfoProvider(props: any) {
return (
<UserinfoContext.Provider value={userinfo}>
{props.children}
</UserinfoContext.Provider>
);
return (
<UserinfoContext.Provider value={userinfo}>
{props.children}
</UserinfoContext.Provider>
);
}
export function useUserinfo() {
return useContext(UserinfoContext);
return useContext(UserinfoContext);
}

View File

@ -12,7 +12,8 @@ content = "uploads"
[identity]
client_id = "goatplaza"
client_secret = "Z9k9AFTj^p"
endpoint = "https://id.smartsheep.studio"
endpoint = "http://localhost:8444"
grpc_endpoint = "127.0.0.1:7444"
[mailer]
name = "Alphabot <alphabot@smartsheep.studio>"
@ -21,6 +22,12 @@ smtp_port = 465
username = "alphabot@smartsheep.studio"
password = "gz937Zxxzfcd9SeH"
[security]
cookie_domain = "localhost"
cookie_samesite = "Lax"
access_token_duration = 300
refresh_token_duration = 2592000
[database]
dsn = "host=localhost dbname=hy_interactive port=5432 sslmode=disable"
prefix = "interactive_"