Files
oauth2-api/app/services/token.go
2026-01-27 11:19:52 +01:00

142 lines
6.1 KiB
Go

package services
import (
"encoding/base64"
"net/http"
"strings"
"time"
"git.secnex.io/secnex/masterlog"
"git.secnex.io/secnex/oauth2-api/config"
"git.secnex.io/secnex/oauth2-api/models"
"git.secnex.io/secnex/oauth2-api/repositories"
"git.secnex.io/secnex/oauth2-api/utils"
"github.com/gofiber/fiber/v2"
"github.com/golang-jwt/jwt/v5"
"github.com/google/uuid"
)
type TokenResponse struct {
TokenType string `json:"token_type"`
Scope string `json:"scope"`
ExpiresIn int `json:"expires_in"`
ExtExpiresIn int `json:"ext_expires_in"`
AccessToken string `json:"access_token"`
RefreshToken string `json:"refresh_token"`
}
func Token(clientID, grantType, code, redirectURI, clientSecret string) *utils.HTTPResponse {
now := time.Now().UTC()
application, err := repositories.GetApplicationByID(clientID)
if err != nil {
masterlog.Debug("Application not found", map[string]interface{}{"error": err.Error(), "client_id": clientID})
return utils.NewHTTPResponse(http.StatusUnauthorized, &fiber.Map{"error": "Application not found"}, "", nil, nil)
}
if application.ExpiresAt.Before(time.Now().UTC()) {
masterlog.Debug("Application expired", map[string]interface{}{"client_id": clientID})
return utils.NewHTTPResponse(http.StatusUnauthorized, &fiber.Map{"error": "Application expired"}, "", nil, nil)
}
valid, err := utils.Verify(clientSecret, application.Secret)
if err != nil {
masterlog.Debug("Invalid client secret", map[string]interface{}{"error": err.Error(), "client_id": clientID})
return utils.NewHTTPResponse(http.StatusUnauthorized, &fiber.Map{"error": "Invalid client secret"}, "", nil, nil)
}
if !valid {
masterlog.Debug("Invalid client secret", map[string]interface{}{"client_id": clientID})
return utils.NewHTTPResponse(http.StatusUnauthorized, &fiber.Map{"error": "Invalid client secret"}, "", nil, nil)
}
authorizationCodePlain, err := base64.StdEncoding.DecodeString(code)
if err != nil {
masterlog.Debug("Invalid authorization code", map[string]interface{}{"error": err.Error(), "client_id": clientID})
return utils.NewHTTPResponse(http.StatusUnauthorized, &fiber.Map{"error": "Invalid authorization code"}, "", nil, nil)
}
authorizationCodeParts := strings.Split(string(authorizationCodePlain), ":")
if len(authorizationCodeParts) != 2 {
masterlog.Debug("Invalid authorization code", map[string]interface{}{"client_id": clientID})
return utils.NewHTTPResponse(http.StatusUnauthorized, &fiber.Map{"error": "Invalid authorization code"}, "", nil, nil)
}
authorizationID := uuid.MustParse(authorizationCodeParts[0])
authorizationCode := authorizationCodeParts[1]
authorization, err := repositories.GetAuthorizationByID(authorizationID.String())
if err != nil {
masterlog.Debug("Authorization not found", map[string]interface{}{"error": err.Error(), "client_id": clientID})
return utils.NewHTTPResponse(http.StatusUnauthorized, &fiber.Map{"error": "Authorization not found"}, "", nil, nil)
}
codeValid, err := utils.Verify(authorizationCode, authorization.Code)
if err != nil {
masterlog.Debug("Invalid authorization code", map[string]interface{}{"error": err.Error(), "client_id": clientID})
return utils.NewHTTPResponse(http.StatusUnauthorized, &fiber.Map{"error": "Invalid authorization code"}, "", nil, nil)
}
if !codeValid {
masterlog.Debug("Invalid authorization code", map[string]interface{}{"client_id": clientID})
return utils.NewHTTPResponse(http.StatusUnauthorized, &fiber.Map{"error": "Invalid authorization code"}, "", nil, nil)
}
tokenID := uuid.New()
refreshToken := utils.GenerateRandomString(64)
token := &models.Token{
ID: tokenID,
RefreshToken: refreshToken,
UserID: authorization.UserID,
}
if err := repositories.CreateToken(token); err != nil {
masterlog.Debug("Failed to create token", map[string]interface{}{"error": err.Error(), "client_id": clientID})
return utils.NewHTTPResponse(http.StatusInternalServerError, &fiber.Map{"error": "Failed to create token"}, "", nil, nil)
}
if err := repositories.ExpireAuthorization(authorizationID.String()); err != nil {
masterlog.Debug("Failed to expire authorization", map[string]interface{}{"error": err.Error(), "client_id": clientID})
return utils.NewHTTPResponse(http.StatusInternalServerError, &fiber.Map{"error": "Failed to expire authorization"}, "", nil, nil)
}
user, err := repositories.GetUserByID(authorization.UserID.String())
if err != nil {
masterlog.Debug("Failed to get user", map[string]interface{}{"error": err.Error(), "client_id": clientID})
return utils.NewHTTPResponse(http.StatusInternalServerError, &fiber.Map{"error": "Failed to get user"}, "", nil, nil)
}
userClaims := map[string]interface{}{
"id": user.ID.String(),
"username": user.Username,
"email": user.Email,
"first_name": user.FirstName,
"last_name": user.LastName,
}
if user.TenantID != nil {
userClaims["tenant_id"] = user.TenantID.String()
}
if user.ExternalID != nil {
userClaims["external_id"] = user.ExternalID.String()
}
accessToken := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
"sub": tokenID.String(),
"exp": token.SessionExpiresAt.Unix(),
"iat": now.Unix(),
"aud": clientID,
"iss": "https://secnex.io",
"user": userClaims,
})
secret := config.CONFIG.JwtSecret
accessTokenString, err := accessToken.SignedString([]byte(secret))
if err != nil {
masterlog.Debug("Failed to sign access token", map[string]interface{}{"error": err.Error(), "client_id": clientID})
return utils.NewHTTPResponse(http.StatusInternalServerError, &fiber.Map{"error": "Failed to sign access token"}, "", nil, nil)
}
tokenExpiresAt := token.SessionExpiresAt.Unix()
extTokenExpiresAt := token.RefreshTokenExpiresAt.Unix()
response := TokenResponse{
TokenType: "Bearer",
Scope: "",
ExpiresIn: int(tokenExpiresAt - now.Unix()),
ExtExpiresIn: int(extTokenExpiresAt - now.Unix()),
AccessToken: accessTokenString,
RefreshToken: refreshToken,
}
masterlog.Debug("Token created successfully", map[string]interface{}{"client_id": clientID})
return utils.NewHTTPResponse(http.StatusOK, &fiber.Map{"response": response}, "", nil, nil)
}