forked from baron/baron-sso
userfront 이력 session ID기반 작업 완료.
This commit is contained in:
@@ -7,6 +7,7 @@ import (
|
||||
"bytes"
|
||||
"context"
|
||||
crand "crypto/rand"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
@@ -41,6 +42,8 @@ const (
|
||||
prefixLoginCodeQr = "login_code_qr:"
|
||||
prefixPollMeta = "poll_meta:"
|
||||
prefixQrRef = "qr_ref:"
|
||||
prefixQrMeta = "qr_meta:"
|
||||
prefixQrApproverSession = "qr_approver_session:"
|
||||
prefixQrPending = "qr_pending:"
|
||||
prefixSignupEmail = "signup:email:"
|
||||
prefixSignupPhone = "signup:phone:"
|
||||
@@ -605,6 +608,8 @@ func (h *AuthHandler) VerifySms(c *fiber.Ctx) error {
|
||||
if authInfo == nil || authInfo.SessionToken == nil || authInfo.SessionToken.JWT == "" {
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Failed to issue session"})
|
||||
}
|
||||
c.Locals("login_id", loginID)
|
||||
setSessionIDLocal(c, authInfo.SessionToken)
|
||||
|
||||
return c.JSON(fiber.Map{
|
||||
"token": authInfo.SessionToken.JWT,
|
||||
@@ -855,6 +860,8 @@ func (h *AuthHandler) VerifyMagicLink(c *fiber.Ctx) error {
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Failed to issue session"})
|
||||
}
|
||||
sessionToken := authInfo.SessionToken.JWT
|
||||
c.Locals("login_id", loginID)
|
||||
setSessionIDLocal(c, authInfo.SessionToken)
|
||||
|
||||
slog.Info("[Verify] Success! Updating Redis session", "pendingRef", pendingRef)
|
||||
sessionData, _ := json.Marshal(map[string]string{
|
||||
@@ -912,6 +919,8 @@ func (h *AuthHandler) VerifyLoginCode(c *fiber.Ctx) error {
|
||||
if authInfo == nil || authInfo.SessionToken == nil || authInfo.SessionToken.JWT == "" {
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Failed to issue session"})
|
||||
}
|
||||
c.Locals("login_id", lookupLoginID)
|
||||
setSessionIDLocal(c, authInfo.SessionToken)
|
||||
|
||||
h.RedisService.Delete(prefixLoginCode + lookupLoginID)
|
||||
h.RedisService.Delete(prefixLoginCodeSmsTarget + lookupLoginID)
|
||||
@@ -995,6 +1004,8 @@ func (h *AuthHandler) VerifyLoginShortCode(c *fiber.Ctx) error {
|
||||
if authInfo == nil || authInfo.SessionToken == nil || authInfo.SessionToken.JWT == "" {
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Failed to issue session"})
|
||||
}
|
||||
c.Locals("login_id", payload.LoginID)
|
||||
setSessionIDLocal(c, authInfo.SessionToken)
|
||||
|
||||
h.RedisService.Delete(prefixLoginCode + payload.LoginID)
|
||||
h.RedisService.Delete(prefixLoginCodeShort + shortCode)
|
||||
@@ -1080,6 +1091,7 @@ func (h *AuthHandler) PasswordLogin(c *fiber.Ctx) error {
|
||||
ale.Status = fiber.StatusOK
|
||||
ale.LatencyMs = time.Since(startTime)
|
||||
ale.SessionJwt = authInfo.SessionToken.JWT
|
||||
setSessionIDLocal(c, authInfo.SessionToken)
|
||||
ale.Log(slog.LevelInfo, "Login successful", slog.String("provider", h.IdpProvider.Name()), slog.String("subject", authInfo.Subject))
|
||||
|
||||
resp := fiber.Map{
|
||||
@@ -1430,6 +1442,7 @@ func (h *AuthHandler) InitQRLogin(c *fiber.Ctx) error {
|
||||
// Redis에 초기 상태 저장 (5분 만료)
|
||||
h.RedisService.Set(prefixSession+pendingRef, fmt.Sprintf(`{"status":"%s"}`, statusPending), 5*time.Minute)
|
||||
h.RedisService.Set(prefixQrRef+qrRef, pendingRef, 5*time.Minute)
|
||||
h.storeQrMeta(pendingRef, c)
|
||||
|
||||
return c.JSON(fiber.Map{
|
||||
"qrCode": qrPayload, // 프론트엔드에서 이 텍스트로 QR을 생성하거나, 이미지를 반환
|
||||
@@ -1514,6 +1527,9 @@ func (h *AuthHandler) ScanQRLogin(c *fiber.Ctx) error {
|
||||
slog.Warn("[QR] Cookie session invalid", "error", err)
|
||||
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{"error": "Invalid session"})
|
||||
}
|
||||
if sessionID, err := h.getKratosSessionIDWithCookie(cookie); err == nil && sessionID != "" {
|
||||
h.storeQrApproverSessionID(pendingRef, sessionID)
|
||||
}
|
||||
loginID := pickLoginIDFromTraits(traits)
|
||||
if loginID == "" {
|
||||
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{"error": "Invalid session"})
|
||||
@@ -1529,18 +1545,30 @@ func (h *AuthHandler) ScanQRLogin(c *fiber.Ctx) error {
|
||||
}
|
||||
|
||||
// 2. 모바일 토큰은 승인 검증용으로만 사용하고, 웹 전용 세션을 새로 발급
|
||||
if sessionToken, err := h.tryIssueDescopeQrSession(c, req.Token); err != nil {
|
||||
if sessionToken, loginID, approvedSessionID, err := h.tryIssueDescopeQrSession(c, req.Token); err != nil {
|
||||
slog.Error("[QR] Issue web session failed", "error", err)
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Failed to issue web session"})
|
||||
} else if sessionToken != "" {
|
||||
} else if sessionToken != nil && sessionToken.JWT != "" {
|
||||
h.storeQrApproverSessionID(pendingRef, approvedSessionID)
|
||||
h.writeQrAuditLog(loginID, pendingRef, sessionToken, approvedSessionID)
|
||||
sessionData, _ := json.Marshal(map[string]string{
|
||||
"status": statusSuccess,
|
||||
"jwt": sessionToken,
|
||||
"jwt": sessionToken.JWT,
|
||||
})
|
||||
h.RedisService.Set(prefixSession+pendingRef, string(sessionData), 5*time.Minute)
|
||||
return c.JSON(fiber.Map{"message": "QR Login Approved"})
|
||||
}
|
||||
|
||||
approvedSessionID := ""
|
||||
if req.Token != "" {
|
||||
if sessionID, err := h.getKratosSessionID(req.Token); err == nil {
|
||||
approvedSessionID = sessionID
|
||||
}
|
||||
}
|
||||
if approvedSessionID != "" {
|
||||
h.storeQrApproverSessionID(pendingRef, approvedSessionID)
|
||||
}
|
||||
|
||||
loginID, err := h.resolveKratosLoginID(req.Token)
|
||||
if err != nil {
|
||||
slog.Warn("[QR] Invalid token", "error", err)
|
||||
@@ -1613,6 +1641,7 @@ func (h *AuthHandler) HandleKratosCourierRelay(c *fiber.Ctx) error {
|
||||
"jwt": authInfo.SessionToken.JWT,
|
||||
})
|
||||
h.RedisService.Set(prefixSession+pendingRef, string(sessionData), loginCodeExpiration)
|
||||
h.writeQrAuditLog(loginID, pendingRef, authInfo.SessionToken, "")
|
||||
h.RedisService.Delete(prefixLoginCodeQrPending + loginID)
|
||||
h.RedisService.Delete(prefixLoginCode + loginID)
|
||||
h.RedisService.Delete(prefixLoginCodeQr + pendingRef)
|
||||
@@ -1640,6 +1669,7 @@ func (h *AuthHandler) HandleKratosCourierRelay(c *fiber.Ctx) error {
|
||||
"jwt": authInfo.SessionToken.JWT,
|
||||
})
|
||||
h.RedisService.Set(prefixSession+pendingRef, string(sessionData), loginCodeExpiration)
|
||||
h.writeQrAuditLog(loginID, pendingRef, authInfo.SessionToken, "")
|
||||
h.RedisService.Delete(prefixQrPending + loginID)
|
||||
h.RedisService.Delete(prefixLoginCode + loginID)
|
||||
h.RedisService.Delete(prefixLoginCodeSmsTarget + loginID)
|
||||
@@ -2083,6 +2113,176 @@ func looksLikeJWT(token string) bool {
|
||||
return strings.Count(token, ".") == 2
|
||||
}
|
||||
|
||||
func setSessionIDLocal(c *fiber.Ctx, token *domain.Token) {
|
||||
if c == nil || token == nil {
|
||||
return
|
||||
}
|
||||
if sessionID := extractSessionIDFromToken(token); sessionID != "" {
|
||||
c.Locals("session_id", sessionID)
|
||||
}
|
||||
}
|
||||
|
||||
func extractSessionIDFromToken(token *domain.Token) string {
|
||||
if token == nil {
|
||||
return ""
|
||||
}
|
||||
if token.SessionID != "" {
|
||||
return token.SessionID
|
||||
}
|
||||
if token.JWT != "" {
|
||||
return extractSessionIDFromJWT(token.JWT)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func extractSessionIDFromJWT(token string) string {
|
||||
if !looksLikeJWT(token) {
|
||||
return ""
|
||||
}
|
||||
parts := strings.Split(token, ".")
|
||||
if len(parts) != 3 {
|
||||
return ""
|
||||
}
|
||||
payload, err := base64.RawURLEncoding.DecodeString(parts[1])
|
||||
if err != nil {
|
||||
payload, err = base64.URLEncoding.DecodeString(parts[1])
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
}
|
||||
var claims map[string]any
|
||||
if err := json.Unmarshal(payload, &claims); err != nil {
|
||||
return ""
|
||||
}
|
||||
for _, key := range []string{"sid", "session_id", "sessionId", "jti"} {
|
||||
if raw, ok := claims[key]; ok {
|
||||
switch value := raw.(type) {
|
||||
case string:
|
||||
if value != "" {
|
||||
return value
|
||||
}
|
||||
default:
|
||||
return fmt.Sprint(value)
|
||||
}
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type qrMeta struct {
|
||||
IPAddress string `json:"ip_address"`
|
||||
UserAgent string `json:"user_agent"`
|
||||
}
|
||||
|
||||
func (h *AuthHandler) storeQrMeta(pendingRef string, c *fiber.Ctx) {
|
||||
if h.RedisService == nil || pendingRef == "" || c == nil {
|
||||
return
|
||||
}
|
||||
meta := qrMeta{
|
||||
IPAddress: extractClientIPFromHeaders(c),
|
||||
UserAgent: c.Get("User-Agent"),
|
||||
}
|
||||
raw, err := json.Marshal(meta)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
_ = h.RedisService.Set(prefixQrMeta+pendingRef, string(raw), 5*time.Minute)
|
||||
}
|
||||
|
||||
func (h *AuthHandler) loadQrMeta(pendingRef string) (qrMeta, bool) {
|
||||
if h.RedisService == nil || pendingRef == "" {
|
||||
return qrMeta{}, false
|
||||
}
|
||||
val, err := h.RedisService.Get(prefixQrMeta + pendingRef)
|
||||
if err != nil || val == "" {
|
||||
return qrMeta{}, false
|
||||
}
|
||||
var meta qrMeta
|
||||
if err := json.Unmarshal([]byte(val), &meta); err != nil {
|
||||
return qrMeta{}, false
|
||||
}
|
||||
return meta, true
|
||||
}
|
||||
|
||||
func (h *AuthHandler) storeQrApproverSessionID(pendingRef, sessionID string) {
|
||||
if h.RedisService == nil || pendingRef == "" || sessionID == "" {
|
||||
return
|
||||
}
|
||||
_ = h.RedisService.Set(prefixQrApproverSession+pendingRef, sessionID, loginCodeExpiration)
|
||||
}
|
||||
|
||||
func (h *AuthHandler) loadQrApproverSessionID(pendingRef string) string {
|
||||
if h.RedisService == nil || pendingRef == "" {
|
||||
return ""
|
||||
}
|
||||
val, err := h.RedisService.Get(prefixQrApproverSession + pendingRef)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return strings.TrimSpace(val)
|
||||
}
|
||||
|
||||
func (h *AuthHandler) writeQrAuditLog(loginID, pendingRef string, sessionToken *domain.Token, approvedSessionID string) {
|
||||
if h.AuditRepo == nil || pendingRef == "" {
|
||||
return
|
||||
}
|
||||
meta, ok := h.loadQrMeta(pendingRef)
|
||||
if !ok {
|
||||
meta = qrMeta{
|
||||
IPAddress: "",
|
||||
UserAgent: "",
|
||||
}
|
||||
}
|
||||
if approvedSessionID == "" {
|
||||
approvedSessionID = h.loadQrApproverSessionID(pendingRef)
|
||||
}
|
||||
sessionID := extractSessionIDFromToken(sessionToken)
|
||||
details := map[string]any{
|
||||
"path": "/api/v1/auth/qr/approve",
|
||||
"login_id": loginID,
|
||||
"pending_ref": pendingRef,
|
||||
}
|
||||
if sessionID != "" {
|
||||
details["session_id"] = sessionID
|
||||
}
|
||||
if approvedSessionID != "" {
|
||||
details["approved_session_id"] = approvedSessionID
|
||||
}
|
||||
detailsJSON, _ := json.Marshal(details)
|
||||
|
||||
log := &domain.AuditLog{
|
||||
EventID: GenerateSecureToken(16),
|
||||
Timestamp: time.Now(),
|
||||
UserID: "",
|
||||
SessionID: sessionID,
|
||||
EventType: "POST /api/v1/auth/qr/approve",
|
||||
Status: "success",
|
||||
IPAddress: meta.IPAddress,
|
||||
UserAgent: meta.UserAgent,
|
||||
Details: string(detailsJSON),
|
||||
AuthMethod: "QR",
|
||||
}
|
||||
_ = h.AuditRepo.Create(log)
|
||||
}
|
||||
|
||||
func extractClientIPFromHeaders(c *fiber.Ctx) string {
|
||||
if c == nil {
|
||||
return ""
|
||||
}
|
||||
if forwarded := c.Get("X-Forwarded-For"); forwarded != "" {
|
||||
parts := strings.Split(forwarded, ",")
|
||||
if len(parts) > 0 {
|
||||
if ip := strings.TrimSpace(parts[0]); ip != "" {
|
||||
return ip
|
||||
}
|
||||
}
|
||||
}
|
||||
if realIP := strings.TrimSpace(c.Get("X-Real-IP")); realIP != "" {
|
||||
return realIP
|
||||
}
|
||||
return c.IP()
|
||||
}
|
||||
|
||||
func (h *AuthHandler) GetAuthTimeline(c *fiber.Ctx) error {
|
||||
if h.AuditRepo == nil {
|
||||
return c.Status(fiber.StatusServiceUnavailable).JSON(fiber.Map{"error": "Audit service unavailable"})
|
||||
@@ -2126,6 +2326,13 @@ func (h *AuthHandler) GetAuthTimeline(c *fiber.Ctx) error {
|
||||
if log.UserID == "" {
|
||||
log.UserID = profile.ID
|
||||
}
|
||||
log.AuthMethod = deriveAuthMethod(log)
|
||||
if log.AuthMethod == "" {
|
||||
continue
|
||||
}
|
||||
if log.SessionID == "" {
|
||||
log.SessionID = extractSessionIDFromAuditDetails(log.Details)
|
||||
}
|
||||
items = append(items, log)
|
||||
if len(items) >= limit {
|
||||
break
|
||||
@@ -2182,6 +2389,141 @@ func isAuthEventType(eventType string) bool {
|
||||
return strings.Contains(normalized, " /api/v1/auth/")
|
||||
}
|
||||
|
||||
func extractAuditPath(log domain.AuditLog) string {
|
||||
if log.Details != "" {
|
||||
if payload, err := parseAuditDetails(log.Details); err == nil {
|
||||
if path, ok := payload["path"].(string); ok && path != "" {
|
||||
return path
|
||||
}
|
||||
}
|
||||
}
|
||||
parts := strings.SplitN(log.EventType, " ", 2)
|
||||
if len(parts) == 2 {
|
||||
return strings.TrimSpace(parts[1])
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func parseAuditDetails(details string) (map[string]any, error) {
|
||||
var payload map[string]any
|
||||
if details == "" {
|
||||
return nil, fmt.Errorf("empty details")
|
||||
}
|
||||
if err := json.Unmarshal([]byte(details), &payload); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return payload, nil
|
||||
}
|
||||
|
||||
func extractRequestBody(details map[string]any) map[string]any {
|
||||
if details == nil {
|
||||
return nil
|
||||
}
|
||||
raw, ok := details["request_body"].(string)
|
||||
if !ok || raw == "" {
|
||||
return nil
|
||||
}
|
||||
var body map[string]any
|
||||
if err := json.Unmarshal([]byte(raw), &body); err != nil {
|
||||
return nil
|
||||
}
|
||||
return body
|
||||
}
|
||||
|
||||
func loginIDKind(loginID string) string {
|
||||
normalized := strings.TrimSpace(loginID)
|
||||
if normalized == "" {
|
||||
return ""
|
||||
}
|
||||
if strings.Contains(normalized, "@") {
|
||||
return "email"
|
||||
}
|
||||
return "phone"
|
||||
}
|
||||
|
||||
func deriveAuthMethod(log domain.AuditLog) string {
|
||||
path := strings.ToLower(extractAuditPath(log))
|
||||
if path == "" {
|
||||
return ""
|
||||
}
|
||||
|
||||
loginID := extractLoginIDFromAuditDetails(log.Details)
|
||||
kind := loginIDKind(loginID)
|
||||
details, _ := parseAuditDetails(log.Details)
|
||||
requestBody := extractRequestBody(details)
|
||||
|
||||
switch {
|
||||
case strings.Contains(path, "/api/v1/auth/password/login"):
|
||||
if kind == "email" {
|
||||
return "비밀번호(Email)"
|
||||
}
|
||||
if kind == "phone" {
|
||||
return "비밀번호(전화번호)"
|
||||
}
|
||||
return "비밀번호"
|
||||
case strings.Contains(path, "/api/v1/auth/enchanted-link/init"):
|
||||
if requestBody != nil {
|
||||
if raw, ok := requestBody["codeOnly"]; ok {
|
||||
if value, ok := raw.(bool); ok && value {
|
||||
if kind == "phone" {
|
||||
return "코드(SMS)"
|
||||
}
|
||||
if kind == "email" {
|
||||
return "코드(Email)"
|
||||
}
|
||||
return "코드"
|
||||
}
|
||||
}
|
||||
}
|
||||
if requestBody != nil {
|
||||
if raw, ok := requestBody["method"].(string); ok {
|
||||
method := strings.ToLower(strings.TrimSpace(raw))
|
||||
if method == "sms" {
|
||||
return "링크(SMS)"
|
||||
}
|
||||
if method == "email" {
|
||||
return "링크(Email)"
|
||||
}
|
||||
}
|
||||
}
|
||||
if kind == "phone" {
|
||||
return "링크(SMS)"
|
||||
}
|
||||
if kind == "email" {
|
||||
return "링크(Email)"
|
||||
}
|
||||
return "링크"
|
||||
case strings.Contains(path, "/api/v1/auth/magic-link/verify"):
|
||||
if kind == "phone" {
|
||||
return "링크(SMS)"
|
||||
}
|
||||
if kind == "email" {
|
||||
return "링크(Email)"
|
||||
}
|
||||
return "링크"
|
||||
case strings.Contains(path, "/api/v1/auth/login/code/verify"):
|
||||
if kind == "phone" {
|
||||
return "코드(SMS)"
|
||||
}
|
||||
if kind == "email" {
|
||||
return "코드(Email)"
|
||||
}
|
||||
return "코드"
|
||||
case strings.Contains(path, "/api/v1/auth/login/code/verify-short"):
|
||||
return "코드(간편)"
|
||||
case strings.Contains(path, "/api/v1/auth/verify-sms"):
|
||||
return "코드(SMS)"
|
||||
case strings.Contains(path, "/api/v1/auth/qr/approve"):
|
||||
return "QR"
|
||||
case strings.Contains(path, "/api/v1/auth/qr/init"):
|
||||
return "QR"
|
||||
case strings.Contains(path, "/api/v1/auth/qr/poll"):
|
||||
return "QR"
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
func buildLoginCandidates(profile *domain.UserProfileResponse) map[string]struct{} {
|
||||
candidates := make(map[string]struct{})
|
||||
if profile == nil {
|
||||
@@ -2256,6 +2598,25 @@ func extractLoginIDFromAuditDetails(details string) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func extractSessionIDFromAuditDetails(details string) string {
|
||||
if details == "" {
|
||||
return ""
|
||||
}
|
||||
var payload map[string]any
|
||||
if err := json.Unmarshal([]byte(details), &payload); err != nil {
|
||||
return ""
|
||||
}
|
||||
if raw, ok := payload["session_id"]; ok {
|
||||
switch value := raw.(type) {
|
||||
case string:
|
||||
return value
|
||||
default:
|
||||
return fmt.Sprint(value)
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (h *AuthHandler) resolveIdentityID(c *fiber.Ctx, token string) (string, error) {
|
||||
if looksLikeJWT(token) && h.DescopeClient != nil {
|
||||
authorized, userToken, err := h.DescopeClient.Auth.ValidateSessionWithToken(c.Context(), token)
|
||||
@@ -2267,26 +2628,26 @@ func (h *AuthHandler) resolveIdentityID(c *fiber.Ctx, token string) (string, err
|
||||
return id, err
|
||||
}
|
||||
|
||||
func (h *AuthHandler) tryIssueDescopeQrSession(c *fiber.Ctx, token string) (string, error) {
|
||||
func (h *AuthHandler) tryIssueDescopeQrSession(c *fiber.Ctx, token string) (*domain.Token, string, string, error) {
|
||||
if !looksLikeJWT(token) || h.DescopeClient == nil {
|
||||
return "", nil
|
||||
return nil, "", "", nil
|
||||
}
|
||||
authorized, userToken, err := h.DescopeClient.Auth.ValidateSessionWithToken(c.Context(), token)
|
||||
if err != nil || !authorized {
|
||||
return "", nil
|
||||
return nil, "", "", nil
|
||||
}
|
||||
loginID, err := h.resolveDescopeLoginID(c.Context(), userToken)
|
||||
if err != nil {
|
||||
return "", err
|
||||
return nil, "", "", err
|
||||
}
|
||||
authInfo, err := h.IdpProvider.IssueSession(loginID)
|
||||
if err != nil {
|
||||
return "", err
|
||||
return nil, "", "", err
|
||||
}
|
||||
if authInfo == nil || authInfo.SessionToken == nil || authInfo.SessionToken.JWT == "" {
|
||||
return "", fmt.Errorf("descope issue session returned empty token")
|
||||
return nil, "", "", fmt.Errorf("descope issue session returned empty token")
|
||||
}
|
||||
return authInfo.SessionToken.JWT, nil
|
||||
return authInfo.SessionToken, loginID, userToken.ID, nil
|
||||
}
|
||||
|
||||
func (h *AuthHandler) resolveKratosLoginID(token string) (string, error) {
|
||||
@@ -2544,6 +2905,36 @@ func (h *AuthHandler) getKratosIdentity(sessionToken string) (string, map[string
|
||||
return result.Identity.ID, result.Identity.Traits, nil
|
||||
}
|
||||
|
||||
func (h *AuthHandler) getKratosSessionID(sessionToken string) (string, error) {
|
||||
kratosURL := strings.TrimRight(os.Getenv("KRATOS_PUBLIC_URL"), "/")
|
||||
if kratosURL == "" {
|
||||
kratosURL = "http://kratos:4433"
|
||||
}
|
||||
req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, kratosURL+"/sessions/whoami", nil)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
req.Header.Set("X-Session-Token", sessionToken)
|
||||
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode >= 300 {
|
||||
body, _ := io.ReadAll(io.LimitReader(resp.Body, 2048))
|
||||
return "", fmt.Errorf("kratos whoami failed status=%d body=%s", resp.StatusCode, string(body))
|
||||
}
|
||||
|
||||
var result struct {
|
||||
ID string `json:"id"`
|
||||
}
|
||||
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return result.ID, nil
|
||||
}
|
||||
|
||||
func (h *AuthHandler) issueKratosSession(ctx context.Context, identityID string) (string, error) {
|
||||
if identityID == "" {
|
||||
return "", fmt.Errorf("kratos identity id is empty")
|
||||
@@ -2621,6 +3012,36 @@ func (h *AuthHandler) getKratosIdentityWithCookie(cookie string) (string, map[st
|
||||
return result.Identity.ID, result.Identity.Traits, nil
|
||||
}
|
||||
|
||||
func (h *AuthHandler) getKratosSessionIDWithCookie(cookie string) (string, error) {
|
||||
kratosURL := strings.TrimRight(os.Getenv("KRATOS_PUBLIC_URL"), "/")
|
||||
if kratosURL == "" {
|
||||
kratosURL = "http://kratos:4433"
|
||||
}
|
||||
req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, kratosURL+"/sessions/whoami", nil)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
req.Header.Set("Cookie", cookie)
|
||||
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode >= 300 {
|
||||
body, _ := io.ReadAll(io.LimitReader(resp.Body, 2048))
|
||||
return "", fmt.Errorf("kratos whoami failed status=%d body=%s", resp.StatusCode, string(body))
|
||||
}
|
||||
|
||||
var result struct {
|
||||
ID string `json:"id"`
|
||||
}
|
||||
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return result.ID, nil
|
||||
}
|
||||
|
||||
func (h *AuthHandler) updateKratosIdentity(identityID string, traits map[string]interface{}) error {
|
||||
kratosAdminURL := strings.TrimRight(os.Getenv("KRATOS_ADMIN_URL"), "/")
|
||||
if kratosAdminURL == "" {
|
||||
|
||||
Reference in New Issue
Block a user