feat(auth): Add /token endpoint to request a access token
This commit is contained in:
141
app/services/token.go
Normal file
141
app/services/token.go
Normal file
@@ -0,0 +1,141 @@
|
||||
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)
|
||||
}
|
||||
Reference in New Issue
Block a user