feat: create service session

This commit is contained in:
2025-06-15 21:02:38 +02:00
parent b36b6e18ca
commit 41d439beab

View File

@ -17,20 +17,10 @@ import (
"github.com/google/uuid" "github.com/google/uuid"
) )
type ApiToken struct { func (h *OAuthHandler) signApiTokens(user *repository.User, apiService *repository.ApiService, nonce *string) (*types.SignedToken, *types.SignedToken, *types.SignedToken, error) {
Token string
Expiration float64
}
type ApiTokens struct {
ID ApiToken
Access ApiToken
Refresh ApiToken
}
func (h *OAuthHandler) signApiTokens(user *repository.User, apiService *repository.ApiService, nonce *string) (*ApiTokens, error) {
accessExpiresIn := 15 * time.Minute accessExpiresIn := 15 * time.Minute
accessExpiresAt := time.Now().Add(accessExpiresIn) accessExpiresAt := time.Now().Add(accessExpiresIn)
accessJTI := uuid.New()
accessClaims := types.ApiClaims{ accessClaims := types.ApiClaims{
Permissions: []string{}, Permissions: []string{},
@ -40,12 +30,13 @@ func (h *OAuthHandler) signApiTokens(user *repository.User, apiService *reposito
Audience: jwt.ClaimStrings{apiService.ClientID}, Audience: jwt.ClaimStrings{apiService.ClientID},
IssuedAt: jwt.NewNumericDate(time.Now()), IssuedAt: jwt.NewNumericDate(time.Now()),
ExpiresAt: jwt.NewNumericDate(accessExpiresAt), ExpiresAt: jwt.NewNumericDate(accessExpiresAt),
ID: accessJTI.String(),
}, },
} }
access, err := util.SignJwtToken(accessClaims, h.cfg.Jwt.PrivateKey) access, err := util.SignJwtToken(accessClaims, h.cfg.Jwt.PrivateKey)
if err != nil { if err != nil {
return nil, err return nil, nil, nil, err
} }
var roles = []string{"user"} var roles = []string{"user"}
@ -56,6 +47,7 @@ func (h *OAuthHandler) signApiTokens(user *repository.User, apiService *reposito
idExpiresIn := 15 * time.Minute idExpiresIn := 15 * time.Minute
idExpiresAt := time.Now().Add(idExpiresIn) idExpiresAt := time.Now().Add(idExpiresIn)
idJTI := uuid.New()
idClaims := types.IdTokenClaims{ idClaims := types.IdTokenClaims{
Email: user.Email, Email: user.Email,
@ -70,16 +62,18 @@ func (h *OAuthHandler) signApiTokens(user *repository.User, apiService *reposito
Audience: jwt.ClaimStrings{apiService.ClientID}, Audience: jwt.ClaimStrings{apiService.ClientID},
IssuedAt: jwt.NewNumericDate(time.Now()), IssuedAt: jwt.NewNumericDate(time.Now()),
ExpiresAt: jwt.NewNumericDate(idExpiresAt), ExpiresAt: jwt.NewNumericDate(idExpiresAt),
ID: idJTI.String(),
}, },
} }
idToken, err := util.SignJwtToken(idClaims, h.cfg.Jwt.PrivateKey) idToken, err := util.SignJwtToken(idClaims, h.cfg.Jwt.PrivateKey)
if err != nil { if err != nil {
return nil, err return nil, nil, nil, err
} }
refreshExpiresIn := 24 * time.Hour refreshExpiresIn := 24 * time.Hour
refreshExpiresAt := time.Now().Add(refreshExpiresIn) refreshExpiresAt := time.Now().Add(refreshExpiresIn)
refreshJTI := uuid.New()
refreshClaims := types.ApiRefreshClaims{ refreshClaims := types.ApiRefreshClaims{
UserID: user.ID.String(), UserID: user.ID.String(),
@ -89,28 +83,16 @@ func (h *OAuthHandler) signApiTokens(user *repository.User, apiService *reposito
Audience: jwt.ClaimStrings{apiService.ClientID}, Audience: jwt.ClaimStrings{apiService.ClientID},
IssuedAt: jwt.NewNumericDate(time.Now()), IssuedAt: jwt.NewNumericDate(time.Now()),
ExpiresAt: jwt.NewNumericDate(refreshExpiresAt), ExpiresAt: jwt.NewNumericDate(refreshExpiresAt),
ID: refreshJTI.String(),
}, },
} }
refresh, err := util.SignJwtToken(refreshClaims, h.cfg.Jwt.PrivateKey) refresh, err := util.SignJwtToken(refreshClaims, h.cfg.Jwt.PrivateKey)
if err != nil { if err != nil {
return nil, err return nil, nil, nil, err
} }
return &ApiTokens{ return types.NewSignedToken(idToken, idExpiresAt, idJTI), types.NewSignedToken(access, accessExpiresAt, accessJTI), types.NewSignedToken(refresh, refreshExpiresAt, refreshJTI), nil
ID: ApiToken{
Token: idToken,
Expiration: idExpiresIn.Seconds(),
},
Access: ApiToken{
Token: access,
Expiration: accessExpiresIn.Seconds(),
},
Refresh: ApiToken{
Token: refresh,
Expiration: refreshExpiresIn.Seconds(),
},
}, nil
} }
func (h *OAuthHandler) tokenEndpoint(w http.ResponseWriter, r *http.Request) { func (h *OAuthHandler) tokenEndpoint(w http.ResponseWriter, r *http.Request) {
@ -174,40 +156,71 @@ func (h *OAuthHandler) tokenEndpoint(w http.ResponseWriter, r *http.Request) {
fmt.Printf("Code received: %s\n", code) fmt.Printf("Code received: %s\n", code)
session, err := h.cache.GetAuthCode(r.Context(), code) codeSession, err := h.cache.GetAuthCode(r.Context(), code)
if err != nil { if err != nil {
log.Printf("ERR: Failed to find session under the code %s: %v\n", code, err) log.Printf("ERR: Failed to find session under the code %s: %v\n", code, err)
web.Error(w, "no session found under this auth code", http.StatusNotFound) web.Error(w, "no session found under this auth code", http.StatusNotFound)
return return
} }
log.Printf("DEBUG: Fetched code session: %#v\n", session) log.Printf("DEBUG: Fetched code session: %#v\n", codeSession)
apiService, err := h.repo.GetApiServiceCID(r.Context(), session.ClientID) apiService, err := h.repo.GetApiServiceCID(r.Context(), codeSession.ClientID)
if err != nil { if err != nil {
log.Printf("ERR: Could not find API service with client %s: %v\n", session.ClientID, err) log.Printf("ERR: Could not find API service with client %s: %v\n", codeSession.ClientID, err)
web.Error(w, "service is not registered", http.StatusForbidden) web.Error(w, "service is not registered", http.StatusForbidden)
return return
} }
if session.ClientID != clientId { if codeSession.ClientID != clientId {
web.Error(w, "invalid auth", http.StatusUnauthorized) web.Error(w, "invalid auth", http.StatusUnauthorized)
return return
} }
user, err := h.repo.FindUserId(r.Context(), uuid.MustParse(session.UserID)) user, err := h.repo.FindUserId(r.Context(), uuid.MustParse(codeSession.UserID))
if err != nil { if err != nil {
web.Error(w, "requested user not found", http.StatusNotFound) web.Error(w, "requested user not found", http.StatusNotFound)
return return
} }
tokens, err := h.signApiTokens(&user, &apiService, &session.Nonce) id, access, refresh, err := h.signApiTokens(&user, &apiService, &codeSession.Nonce)
if err != nil { if err != nil {
log.Println("ERR: Failed to sign api tokens:", err) log.Println("ERR: Failed to sign api tokens:", err)
web.Error(w, "failed to sign tokens", http.StatusInternalServerError) web.Error(w, "failed to sign tokens", http.StatusInternalServerError)
return return
} }
log.Printf("DEBUG: Created api tokens: %v\n\n%v\n\n%v\n", id.ID.String(), access.ID.String(), refresh.ID.String())
userId, err := uuid.Parse(codeSession.UserID)
if err != nil {
log.Printf("ERR: Failed to parse user '%s' uuid: %v\n", codeSession.UserID, err)
web.Error(w, "failed to sign tokens", http.StatusInternalServerError)
return
}
ipAddr := util.GetClientIP(r)
ua := r.UserAgent()
session, err := h.repo.CreateServiceSession(r.Context(), repository.CreateServiceSessionParams{
ServiceID: apiService.ID,
ClientID: apiService.ClientID,
UserID: &userId,
ExpiresAt: &refresh.ExpiresAt,
LastActive: nil,
IpAddress: &ipAddr,
UserAgent: &ua,
AccessTokenID: &access.ID,
RefreshTokenID: &refresh.ID,
})
if err != nil {
log.Printf("ERR: Failed to create new service session: %v\n", err)
web.Error(w, "failed to create session", http.StatusInternalServerError)
return
}
log.Printf("INFO: Service session created for '%s' client_id with '%s' id\n", apiService.ClientID, session.ID.String())
type Response struct { type Response struct {
IdToken string `json:"id_token"` IdToken string `json:"id_token"`
TokenType string `json:"token_type"` TokenType string `json:"token_type"`
@ -219,11 +232,11 @@ func (h *OAuthHandler) tokenEndpoint(w http.ResponseWriter, r *http.Request) {
} }
response := Response{ response := Response{
IdToken: tokens.ID.Token, IdToken: id.Token,
TokenType: "Bearer", TokenType: "Bearer",
AccessToken: tokens.Access.Token, AccessToken: access.Token,
RefreshToken: tokens.Refresh.Token, RefreshToken: refresh.Token,
ExpiresIn: tokens.Access.Expiration, ExpiresIn: access.ExpiresAt.Sub(time.Now()).Seconds(),
Email: user.Email, Email: user.Email,
} }
@ -269,7 +282,7 @@ func (h *OAuthHandler) tokenEndpoint(w http.ResponseWriter, r *http.Request) {
return return
} }
tokens, err := h.signApiTokens(&user, &apiService, nil) id, access, refresh, err := h.signApiTokens(&user, &apiService, nil)
type Response struct { type Response struct {
IdToken string `json:"id_token"` IdToken string `json:"id_token"`
@ -280,11 +293,11 @@ func (h *OAuthHandler) tokenEndpoint(w http.ResponseWriter, r *http.Request) {
} }
response := Response{ response := Response{
IdToken: tokens.ID.Token, IdToken: id.Token,
TokenType: "Bearer", TokenType: "Bearer",
AccessToken: tokens.Access.Token, AccessToken: access.Token,
RefreshToken: tokens.Refresh.Token, RefreshToken: refresh.Token,
ExpiresIn: tokens.Access.Expiration, ExpiresIn: access.ExpiresAt.Sub(time.Now()).Seconds(),
} }
log.Printf("DEBUG: refresh - sending following response: %#v\n", response) log.Printf("DEBUG: refresh - sending following response: %#v\n", response)