feat: open API v1 and postman collection

This commit is contained in:
Adnan Zahir
2026-04-14 15:14:31 +07:00
parent fbe0634d46
commit 1ab16cfe06
18 changed files with 14668 additions and 4 deletions
+86 -2
View File
@@ -1,9 +1,13 @@
package middleware
import (
"context"
"errors"
"strings"
"sync"
"github.com/gofiber/fiber/v2"
"gitlab.com/mbugroup/lti-api.git/internal/apikeys"
"gitlab.com/mbugroup/lti-api.git/internal/config"
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
"gitlab.com/mbugroup/lti-api.git/internal/modules/sso/session"
@@ -17,11 +21,21 @@ const (
authUserLocalsKey = "auth.user"
)
var (
verifyAccessTokenFunc = sso.VerifyAccessToken
fetchProfileFunc = sso.FetchProfile
apiKeyAuthMu sync.RWMutex
apiKeyAuthenticator apikeys.Authenticator
)
// AuthContext keeps authentication details captured by the middleware.
type AuthContext struct {
Token string
Verification *sso.VerificationResult
User *entity.User
PrincipalType string
PrincipalName string
Roles []sso.Role
Permissions map[string]struct{}
UserAreaIDs []uint
@@ -30,6 +44,13 @@ type AuthContext struct {
UserAllLocation bool
}
func SetAPIKeyAuthenticator(authenticator apikeys.Authenticator) {
apiKeyAuthMu.Lock()
defer apiKeyAuthMu.Unlock()
apiKeyAuthenticator = authenticator
}
// Auth validates the incoming request against the central SSO access token and
// loads the corresponding local user. Optional scopes can be provided to enforce
// fine-grained authorization using the SSO access token scopes.
@@ -62,10 +83,20 @@ func Auth(userService service.UserService, requiredScopes ...string) fiber.Handl
}
}
if token == "" {
if c.Method() == fiber.MethodGet {
if err := authenticateAPIKey(c); err == nil {
if len(requiredScopes) > 0 {
return fiber.NewError(fiber.StatusForbidden, "Insufficient scope")
}
return c.Next()
} else if err != nil && !errors.Is(err, apikeys.ErrInvalidAPIKey) && !errors.Is(err, apikeys.ErrInactiveKey) {
return err
}
}
return fiber.NewError(fiber.StatusUnauthorized, "Please authenticate")
}
verification, err := sso.VerifyAccessToken(token)
verification, err := verifyAccessTokenFunc(token)
if err != nil {
if sso.IsSignatureError(err) {
logSignatureError("auth", tokenSource, token, err)
@@ -99,7 +130,7 @@ func Auth(userService service.UserService, requiredScopes ...string) fiber.Handl
permissions := make(map[string]struct{})
var profile *sso.UserProfile
if verification.UserID != 0 {
if p, err := sso.FetchProfile(c.Context(), token, verification); err != nil {
if p, err := fetchProfileFunc(c.Context(), token, verification); err != nil {
utils.Log.WithError(err).Warn("auth: failed to fetch sso profile")
} else {
profile = p
@@ -118,6 +149,8 @@ func Auth(userService service.UserService, requiredScopes ...string) fiber.Handl
Token: token,
Verification: verification,
User: user,
PrincipalType: "user",
PrincipalName: user.Name,
Roles: roles,
Permissions: permissions,
UserAreaIDs: nil,
@@ -219,6 +252,57 @@ func bearerToken(c *fiber.Ctx) string {
return ""
}
func authenticateAPIKey(c *fiber.Ctx) error {
rawKey := strings.TrimSpace(c.Get("X-API-Key"))
if rawKey == "" {
return apikeys.ErrInvalidAPIKey
}
authenticator := currentAPIKeyAuthenticator()
if authenticator == nil {
return apikeys.ErrInvalidAPIKey
}
principal, err := authenticator.Authenticate(context.Background(), rawKey, c.IP())
if err != nil {
if errors.Is(err, apikeys.ErrInvalidAPIKey) || errors.Is(err, apikeys.ErrInactiveKey) {
return apikeys.ErrInvalidAPIKey
}
utils.Log.WithError(err).Warn("auth: api key authentication failed")
return fiber.NewError(fiber.StatusInternalServerError, "Failed to authenticate request")
}
permissions := make(map[string]struct{}, len(principal.Permissions))
for _, perm := range principal.Permissions {
if canonical := canonicalPermission(perm); canonical != "" {
permissions[canonical] = struct{}{}
}
}
c.Locals(authContextLocalsKey, &AuthContext{
Token: "",
Verification: nil,
User: nil,
PrincipalType: "api_key",
PrincipalName: principal.Name,
Roles: nil,
Permissions: permissions,
UserAreaIDs: principal.AreaIDs,
UserLocationIDs: principal.LocationIDs,
UserAllArea: principal.AllArea,
UserAllLocation: principal.AllLocation,
})
c.Locals(authUserLocalsKey, nil)
return nil
}
func currentAPIKeyAuthenticator() apikeys.Authenticator {
apiKeyAuthMu.RLock()
defer apiKeyAuthMu.RUnlock()
return apiKeyAuthenticator
}
func hasAllScopes(have, required []string) bool {
if len(required) == 0 {
return true