diff --git a/frontend/src/apis/apis.ts b/frontend/src/apis/apis.ts index 89b1b00..c44c560 100644 --- a/frontend/src/apis/apis.ts +++ b/frontend/src/apis/apis.ts @@ -1,4 +1,5 @@ -import type { tokenData } from '@/apis/model' +import type { tokenData, ApiUser } from '@/apis/model' +import { apiWrapGet } from '@/apis/utils' export async function login(username: string, password: string) { const v = await fetch(import.meta.env.VITE_APIADDR + "/api/yggdrasil/authserver/authenticate", { @@ -25,9 +26,16 @@ export async function register(email: string, username: string, password: string "CaptchaToken": captchaToken }) }) - const data = await v.json() - if (!v.ok) { - throw data.msg - } - return -} \ No newline at end of file + return await apiWrapGet(v) +} + +export async function userInfo(token: string) { + if (token == "") return + const v = await fetch(import.meta.env.VITE_APIADDR + "/api/v1/user", { + headers: { + "Authorization": "Bearer " + token + } + }) + return await apiWrapGet(v) +} + diff --git a/frontend/src/apis/model.ts b/frontend/src/apis/model.ts index 1792e57..cee32d3 100644 --- a/frontend/src/apis/model.ts +++ b/frontend/src/apis/model.ts @@ -18,4 +18,11 @@ interface captcha { siteKey: string } -export type ApiCaptcha = Api \ No newline at end of file +export type ApiCaptcha = Api + +interface user { + uid: string + uuid: string +} + +export type ApiUser = Api diff --git a/frontend/src/apis/utils.ts b/frontend/src/apis/utils.ts new file mode 100644 index 0000000..6737959 --- /dev/null +++ b/frontend/src/apis/utils.ts @@ -0,0 +1,7 @@ +export async function apiWrapGet(v: Response) { + const data = await v.json() + if (!v.ok) { + throw data.msg + } + return data as T +} diff --git a/frontend/src/components/CaptchaWidget.tsx b/frontend/src/components/CaptchaWidget.tsx index 1a9ec90..65b60d1 100644 --- a/frontend/src/components/CaptchaWidget.tsx +++ b/frontend/src/components/CaptchaWidget.tsx @@ -20,7 +20,7 @@ const CaptchaWidget = forwardRef(({ onSuccess }, ref) => { const Turnstileref = useRef(null) const [key, setKey] = useState(1) const { data, error, loading } = useRequest(() => fetch(import.meta.env.VITE_APIADDR + '/api/v1/captcha').then(v => v.json() as Promise), { - loadingDelay: 500 + loadingDelay: 200 }) useImperativeHandle(ref, () => { diff --git a/frontend/src/views/User.tsx b/frontend/src/views/User.tsx index 8357440..eda784d 100644 --- a/frontend/src/views/User.tsx +++ b/frontend/src/views/User.tsx @@ -1,15 +1,28 @@ import { token, username } from "@/store/store" +import { useRequest } from "ahooks" import { useAtomValue } from "jotai" +import { userInfo } from '@/apis/apis' export default function User() { const nowToken = useAtomValue(token) const nowUsername = useAtomValue(username) + const { data, error } = useRequest(() => userInfo(nowToken), { + refreshDeps: [nowToken], + cacheKey: "/api/v1/user/reg", + cacheTime: 10000 + }) + return ( <>

你好: {nowUsername}

token: {nowToken}

+ {error && String(error)} + {!error && <> +

uid: {data?.data.uid}

+

uuid: {data?.data.uuid}

+ } ) } \ No newline at end of file diff --git a/handle/handle.go b/handle/handle.go index 08aa07d..b28911e 100644 --- a/handle/handle.go +++ b/handle/handle.go @@ -1,10 +1,19 @@ package handle import ( + "context" + "encoding/json" + "fmt" + "io" "log/slog" + "net/http" + "net/netip" + "strings" "github.com/go-playground/validator/v10" + "github.com/samber/lo" "github.com/xmdhs/authlib-skin/config" + "github.com/xmdhs/authlib-skin/model" "github.com/xmdhs/authlib-skin/service" ) @@ -23,3 +32,34 @@ func NewHandel(webService *service.WebService, validate *validator.Validate, con logger: logger, } } + +func encodeJson[T any](w io.Writer, m model.API[T]) { + json.NewEncoder(w).Encode(m) +} + +func (h *Handel) getTokenbyAuthorization(ctx context.Context, w http.ResponseWriter, r *http.Request) string { + auth := r.Header.Get("Authorization") + if auth == "" { + h.logger.DebugContext(ctx, "缺少 Authorization") + handleError(ctx, w, "缺少 Authorization", model.ErrAuth, 401) + return "" + } + al := strings.Split(auth, " ") + if len(al) != 2 || al[0] != "Bearer" { + h.logger.DebugContext(ctx, "Authorization 格式错误") + handleError(ctx, w, "Authorization 格式错误", model.ErrAuth, 401) + return "" + } + return al[1] +} + +func getPrefix(ip string) (string, error) { + ipa, err := netip.ParseAddr(ip) + if err != nil { + return "", fmt.Errorf("getPrefix: %w", err) + } + if ipa.Is6() { + return lo.Must1(ipa.Prefix(48)).String(), nil + } + return lo.Must1(ipa.Prefix(24)).String(), nil +} diff --git a/handle/user.go b/handle/user.go index 9d292bc..1bf7782 100644 --- a/handle/user.go +++ b/handle/user.go @@ -1,16 +1,13 @@ package handle import ( - "encoding/json" "errors" - "fmt" "net/http" - "net/netip" "github.com/julienschmidt/httprouter" - "github.com/samber/lo" "github.com/xmdhs/authlib-skin/model" "github.com/xmdhs/authlib-skin/service" + utilsService "github.com/xmdhs/authlib-skin/service/utils" "github.com/xmdhs/authlib-skin/utils" ) @@ -53,22 +50,34 @@ func (h *Handel) Reg() httprouter.Handle { handleError(ctx, w, err.Error(), model.ErrService, 500) return } - json.NewEncoder(w).Encode(model.API[any]{ + encodeJson(w, model.API[any]{ Code: 0, - Data: nil, - Msg: "", }) - } } -func getPrefix(ip string) (string, error) { - ipa, err := netip.ParseAddr(ip) - if err != nil { - return "", fmt.Errorf("getPrefix: %w", err) +func (h *Handel) UserInfo() httprouter.Handle { + return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) { + ctx := r.Context() + token := h.getTokenbyAuthorization(ctx, w, r) + if token == "" { + return + } + + u, err := h.webService.Info(ctx, token) + if err != nil { + if errors.Is(err, utilsService.ErrTokenInvalid) { + h.logger.DebugContext(ctx, "token 无效") + handleError(ctx, w, "token 无效", model.ErrAuth, 401) + return + } + h.logger.InfoContext(ctx, err.Error()) + handleError(ctx, w, err.Error(), model.ErrUnknown, 500) + return + } + encodeJson(w, model.API[model.UserInfo]{ + Code: 0, + Data: u, + }) } - if ipa.Is6() { - return lo.Must1(ipa.Prefix(48)).String(), nil - } - return lo.Must1(ipa.Prefix(24)).String(), nil } diff --git a/model/const.go b/model/const.go index 6574d29..4ab0f5f 100644 --- a/model/const.go +++ b/model/const.go @@ -9,4 +9,5 @@ const ( ErrService ErrExistUser ErrRegLimit + ErrAuth ) diff --git a/model/model.go b/model/model.go index f55d526..9bc94c9 100644 --- a/model/model.go +++ b/model/model.go @@ -29,3 +29,8 @@ type Captcha struct { Type string `json:"type"` SiteKey string `json:"siteKey"` } + +type UserInfo struct { + UID int `json:"uid"` + UUID string `json:"uuid"` +} diff --git a/server/route/route.go b/server/route/route.go index 3c13a32..8a0f0bc 100644 --- a/server/route/route.go +++ b/server/route/route.go @@ -11,6 +11,10 @@ import ( func NewRoute(yggService *yggdrasil.Yggdrasil, handel *handle.Handel) (*httprouter.Router, error) { r := httprouter.New() + r.HandleOPTIONS = true + r.GlobalOPTIONS = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(204) + }) err := newYggdrasil(r, *yggService) if err != nil { return nil, fmt.Errorf("NewRoute: %w", err) @@ -19,17 +23,6 @@ func NewRoute(yggService *yggdrasil.Yggdrasil, handel *handle.Handel) (*httprout if err != nil { return nil, fmt.Errorf("NewRoute: %w", err) } - r.GlobalOPTIONS = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - if r.Header.Get("Access-Control-Request-Method") != "" { - // Set CORS headers - header := w.Header() - header.Set("Access-Control-Allow-Methods", header.Get("Allow")) - header.Set("Access-Control-Allow-Origin", "*") - } - - // Adjust status code to 204 - w.WriteHeader(http.StatusNoContent) - }) return r, nil } @@ -61,5 +54,6 @@ func newYggdrasil(r *httprouter.Router, handelY yggdrasil.Yggdrasil) error { func newSkinApi(r *httprouter.Router, handel *handle.Handel) error { r.PUT("/api/v1/user/reg", handel.Reg()) r.GET("/api/v1/captcha", handel.GetCaptcha()) + r.GET("/api/v1/user", handel.UserInfo()) return nil } diff --git a/server/server.go b/server/server.go index 263e7cf..caf0ea6 100644 --- a/server/server.go +++ b/server/server.go @@ -33,9 +33,22 @@ func NewServer(c config.Config, sl *slog.Logger, route *httprouter.Router) (*htt if c.Debug && sl.Enabled(ctx, slog.LevelDebug) { sl.DebugContext(ctx, r.Method) } - w.Header().Set("Access-Control-Allow-Origin", "*") - route.ServeHTTP(w, r) + cors(route).ServeHTTP(w, r) }), } return s, func() { s.Close() } } + +func cors(h http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + header := w.Header() + if r.Header.Get("Access-Control-Request-Method") != "" { + header.Set("Access-Control-Allow-Methods", r.Header.Get("Access-Control-Request-Method")) + } + header.Set("Access-Control-Allow-Origin", "*") + header.Set("Access-Control-Allow-Headers", "*") + header.Set("Access-Control-Allow-Private-Network", "true") + header.Set("Access-Control-Max-Age", "3600") + h.ServeHTTP(w, r) + }) +} diff --git a/server/wire_gen.go b/server/wire_gen.go index bf8e827..ec3affd 100644 --- a/server/wire_gen.go +++ b/server/wire_gen.go @@ -52,7 +52,7 @@ func InitializeRoute(ctx context.Context, c config.Config) (*http.Server, func() } yggdrasil3 := yggdrasil2.NewYggdrasil(logger, validate, yggdrasilYggdrasil, c, pubRsaKey) httpClient := ProvideHttpClient() - webService := service.NewWebService(c, client, httpClient) + webService := service.NewWebService(c, client, httpClient, cache, privateKey) handel := handle.NewHandel(webService, validate, c, logger) router, err := route.NewRoute(yggdrasil3, handel) if err != nil { diff --git a/service/user.go b/service/user.go index 4590083..e9933d3 100644 --- a/service/user.go +++ b/service/user.go @@ -12,6 +12,8 @@ import ( "github.com/xmdhs/authlib-skin/db/ent/user" "github.com/xmdhs/authlib-skin/db/ent/userprofile" "github.com/xmdhs/authlib-skin/model" + "github.com/xmdhs/authlib-skin/model/yggdrasil" + utilsService "github.com/xmdhs/authlib-skin/service/utils" "github.com/xmdhs/authlib-skin/utils" ) @@ -88,3 +90,14 @@ func (w *WebService) Reg(ctx context.Context, u model.User, ipPrefix, ip string) } return nil } + +func (w *WebService) Info(ctx context.Context, token string) (model.UserInfo, error) { + t, err := utilsService.Auth(ctx, yggdrasil.ValidateToken{AccessToken: token}, w.client, w.cache, &w.prikey.PublicKey, false) + if err != nil { + return model.UserInfo{}, fmt.Errorf("Info: %w", err) + } + return model.UserInfo{ + UID: t.UID, + UUID: t.Subject, + }, nil +} diff --git a/service/web.go b/service/web.go index 2aa34e5..e59b725 100644 --- a/service/web.go +++ b/service/web.go @@ -1,9 +1,11 @@ package service import ( + "crypto/rsa" "net/http" "github.com/xmdhs/authlib-skin/config" + "github.com/xmdhs/authlib-skin/db/cache" "github.com/xmdhs/authlib-skin/db/ent" ) @@ -11,12 +13,16 @@ type WebService struct { config config.Config client *ent.Client httpClient *http.Client + cache cache.Cache + prikey *rsa.PrivateKey } -func NewWebService(c config.Config, e *ent.Client, hc *http.Client) *WebService { +func NewWebService(c config.Config, e *ent.Client, hc *http.Client, cache cache.Cache, prikey *rsa.PrivateKey) *WebService { return &WebService{ config: c, client: e, httpClient: hc, + cache: cache, + prikey: prikey, } }