142 lines
6.1 KiB
Go
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)
|
|
}
|