mirror of
https://github.com/steveiliop56/tinyauth.git
synced 2026-07-06 18:20:15 +00:00
feat: add swagger comments for context, health, oauth and oidc controllers
This commit is contained in:
@@ -16,7 +16,7 @@ PROD_COMPOSE := $(shell test -f "docker-compose.test.prod.yml" && echo "docker-c
|
|||||||
|
|
||||||
.DEFAULT_GOAL := binary
|
.DEFAULT_GOAL := binary
|
||||||
|
|
||||||
.PHONY: deps clean-data clean-webui webui binary binary-linux-amd64 binary-linux-arm64 test vet test-race dev dev-infisical prod prod-infisical sql generate docker docker-distroless swagger
|
.PHONY: deps clean-data clean-webui webui binary binary-linux-amd64 binary-linux-arm64 test vet test-race dev dev-infisical prod prod-infisical sql generate docker docker-distroless swagger swagger-fmt
|
||||||
|
|
||||||
# Deps
|
# Deps
|
||||||
deps:
|
deps:
|
||||||
@@ -105,4 +105,8 @@ docker-distroless:
|
|||||||
|
|
||||||
# Swagger
|
# Swagger
|
||||||
swagger:
|
swagger:
|
||||||
swag init -d ./internal/bootstrap -g router_bootstrap.go -o ./internal/swagger
|
swag init -d ./internal -g bootstrap/router_bootstrap.go -o ./internal/swagger
|
||||||
|
|
||||||
|
# Swagger Format
|
||||||
|
swagger-fmt:
|
||||||
|
swag fmt -d ./internal -g bootstrap/router_bootstrap.go
|
||||||
@@ -27,7 +27,7 @@ import (
|
|||||||
// @description Swagger documentation for Tinyauth's API.
|
// @description Swagger documentation for Tinyauth's API.
|
||||||
// @license.name AGPL-3.0
|
// @license.name AGPL-3.0
|
||||||
// @license.url https://github.com/tinyauthapp/tinyauth/blob/main/LICENSE
|
// @license.url https://github.com/tinyauthapp/tinyauth/blob/main/LICENSE
|
||||||
// @BasePath /api
|
// @BasePath /
|
||||||
func (app *BootstrapApp) setupRouter() error {
|
func (app *BootstrapApp) setupRouter() error {
|
||||||
// we don't want gin debug mode
|
// we don't want gin debug mode
|
||||||
gin.SetMode(gin.ReleaseMode)
|
gin.SetMode(gin.ReleaseMode)
|
||||||
|
|||||||
@@ -107,6 +107,14 @@ func NewContextController(i ContextControllerInput) *ContextController {
|
|||||||
return controller
|
return controller
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UserContext godoc
|
||||||
|
//
|
||||||
|
// @Summary User context
|
||||||
|
// @Description Get the user context
|
||||||
|
// @Tags context
|
||||||
|
// @Produce json
|
||||||
|
// @Success 200 {object} UserContextResponse
|
||||||
|
// @Router /api/context/user [get]
|
||||||
func (controller *ContextController) userContextHandler(c *gin.Context) {
|
func (controller *ContextController) userContextHandler(c *gin.Context) {
|
||||||
context, err := new(model.UserContext).NewFromGin(c)
|
context, err := new(model.UserContext).NewFromGin(c)
|
||||||
|
|
||||||
@@ -147,6 +155,14 @@ func (controller *ContextController) userContextHandler(c *gin.Context) {
|
|||||||
c.JSON(200, userContext)
|
c.JSON(200, userContext)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AppContext godoc
|
||||||
|
//
|
||||||
|
// @Summary App context
|
||||||
|
// @Description Get the app context
|
||||||
|
// @Tags context
|
||||||
|
// @Produce json
|
||||||
|
// @Success 200 {object} AppContextResponse
|
||||||
|
// @Router /api/context/app [get]
|
||||||
func (controller *ContextController) appContextHandler(c *gin.Context) {
|
func (controller *ContextController) appContextHandler(c *gin.Context) {
|
||||||
c.JSON(200, AppContextResponse{
|
c.JSON(200, AppContextResponse{
|
||||||
Status: 200,
|
Status: 200,
|
||||||
|
|||||||
@@ -7,6 +7,10 @@ const (
|
|||||||
FrontendLoginForApp FrontendLoginFor = "app"
|
FrontendLoginForApp FrontendLoginFor = "app"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type SimpleResponse struct {
|
||||||
|
Status int `json:"status"`
|
||||||
|
Message string `json:"message,omitempty"`
|
||||||
|
}
|
||||||
type UnauthorizedQuery struct {
|
type UnauthorizedQuery struct {
|
||||||
Username string `url:"username"`
|
Username string `url:"username"`
|
||||||
Resource string `url:"resource"`
|
Resource string `url:"resource"`
|
||||||
|
|||||||
@@ -23,9 +23,18 @@ func NewHealthController(i HealthControllerInput) *HealthController {
|
|||||||
return controller
|
return controller
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// HealthCheck godoc
|
||||||
|
//
|
||||||
|
// @Summary Healthcheck
|
||||||
|
// @Description Check if the server is up and running
|
||||||
|
// @Tags health
|
||||||
|
// @Produce json
|
||||||
|
// @Success 200 {object} SimpleResponse
|
||||||
|
// @Router /api/healthz [get]
|
||||||
|
// @Router /api/healthz [head]
|
||||||
func (controller *HealthController) healthHandler(c *gin.Context) {
|
func (controller *HealthController) healthHandler(c *gin.Context) {
|
||||||
c.JSON(200, gin.H{
|
c.JSON(200, SimpleResponse{
|
||||||
"status": 200,
|
Status: 200,
|
||||||
"message": "Healthy",
|
Message: "OK",
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -54,6 +54,27 @@ func NewOAuthController(i OAuthControllerInput) *OAuthController {
|
|||||||
return controller
|
return controller
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type OAuthURLSuccessResponse struct {
|
||||||
|
SimpleResponse
|
||||||
|
URL string `json:"url"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// OAuthURL godoc
|
||||||
|
//
|
||||||
|
// @Summary OAuth URL
|
||||||
|
// @Description Get an OAuth URL for the specified provider
|
||||||
|
// @Tags oauth
|
||||||
|
// @Produce json
|
||||||
|
// @Param id path string true "Provider ID"
|
||||||
|
// @Param login_for query string false "Login for"
|
||||||
|
// @Param oidc_ticket query string false "OpenID Connect Ticket"
|
||||||
|
// @Param oidc_scope query string false "OpenID Connect Scope"
|
||||||
|
// @Param oidc_name query string false "OpenID Connect Name"
|
||||||
|
// @Param redirect_uri query string false "Redirect URI"
|
||||||
|
// @Success 200 {object} OAuthURLSuccessResponse
|
||||||
|
// @Failure 400 {object} SimpleResponse
|
||||||
|
// @Failure 500 {object} SimpleResponse
|
||||||
|
// @Router /api/oauth/url/{id} [get]
|
||||||
func (controller *OAuthController) oauthURLHandler(c *gin.Context) {
|
func (controller *OAuthController) oauthURLHandler(c *gin.Context) {
|
||||||
var req OAuthRequest
|
var req OAuthRequest
|
||||||
|
|
||||||
@@ -111,23 +132,33 @@ func (controller *OAuthController) oauthURLHandler(c *gin.Context) {
|
|||||||
|
|
||||||
c.SetCookie(controller.runtime.OAuthSessionCookieName, sessionId, int(time.Hour.Seconds()), "/", controller.getCookieDomain(), controller.config.Auth.SecureCookie, true)
|
c.SetCookie(controller.runtime.OAuthSessionCookieName, sessionId, int(time.Hour.Seconds()), "/", controller.getCookieDomain(), controller.config.Auth.SecureCookie, true)
|
||||||
|
|
||||||
c.JSON(200, gin.H{
|
c.JSON(200, OAuthURLSuccessResponse{
|
||||||
"status": 200,
|
SimpleResponse: SimpleResponse{
|
||||||
"message": "OK",
|
Status: 200,
|
||||||
"url": authUrl,
|
Message: "OK",
|
||||||
|
},
|
||||||
|
URL: authUrl,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// OAuthCallback godoc
|
||||||
|
//
|
||||||
|
// @Summary OAuth Callback
|
||||||
|
// @Description Callback URL for OAuth providers
|
||||||
|
// @Tags oauth
|
||||||
|
// @Param id path string true "Provider ID"
|
||||||
|
// @Param code query string true "State"
|
||||||
|
// @Param state query string true "Code"
|
||||||
|
// @Success 302
|
||||||
|
// @Failure 302
|
||||||
|
// @Router /api/oauth/callback/{id} [get]
|
||||||
func (controller *OAuthController) oauthCallbackHandler(c *gin.Context) {
|
func (controller *OAuthController) oauthCallbackHandler(c *gin.Context) {
|
||||||
var req OAuthRequest
|
var req OAuthRequest
|
||||||
|
|
||||||
err := c.BindUri(&req)
|
err := c.BindUri(&req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
controller.log.App.Error().Err(err).Msg("Failed to bind URI")
|
controller.log.App.Error().Err(err).Msg("Failed to get provider ID")
|
||||||
c.JSON(400, gin.H{
|
c.Redirect(http.StatusFound, fmt.Sprintf("%s/error", controller.runtime.AppURL))
|
||||||
"status": 400,
|
|
||||||
"message": "Bad Request",
|
|
||||||
})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -135,7 +166,7 @@ func (controller *OAuthController) oauthCallbackHandler(c *gin.Context) {
|
|||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
controller.log.App.Error().Err(err).Msg("Failed to get OAuth session cookie")
|
controller.log.App.Error().Err(err).Msg("Failed to get OAuth session cookie")
|
||||||
c.Redirect(http.StatusTemporaryRedirect, fmt.Sprintf("%s/error", controller.runtime.AppURL))
|
c.Redirect(http.StatusFound, fmt.Sprintf("%s/error", controller.runtime.AppURL))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -145,7 +176,7 @@ func (controller *OAuthController) oauthCallbackHandler(c *gin.Context) {
|
|||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
controller.log.App.Error().Err(err).Msg("Failed to get pending OAuth session")
|
controller.log.App.Error().Err(err).Msg("Failed to get pending OAuth session")
|
||||||
c.Redirect(http.StatusTemporaryRedirect, fmt.Sprintf("%s/error", controller.runtime.AppURL))
|
c.Redirect(http.StatusFound, fmt.Sprintf("%s/error", controller.runtime.AppURL))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -154,7 +185,7 @@ func (controller *OAuthController) oauthCallbackHandler(c *gin.Context) {
|
|||||||
state := c.Query("state")
|
state := c.Query("state")
|
||||||
if state != oauthPendingSession.State {
|
if state != oauthPendingSession.State {
|
||||||
controller.log.App.Warn().Msg("OAuth state mismatch")
|
controller.log.App.Warn().Msg("OAuth state mismatch")
|
||||||
c.Redirect(http.StatusTemporaryRedirect, fmt.Sprintf("%s/error", controller.runtime.AppURL))
|
c.Redirect(http.StatusFound, fmt.Sprintf("%s/error", controller.runtime.AppURL))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -163,7 +194,7 @@ func (controller *OAuthController) oauthCallbackHandler(c *gin.Context) {
|
|||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
controller.log.App.Error().Err(err).Msg("Failed to exchange code for token")
|
controller.log.App.Error().Err(err).Msg("Failed to exchange code for token")
|
||||||
c.Redirect(http.StatusTemporaryRedirect, fmt.Sprintf("%s/error", controller.runtime.AppURL))
|
c.Redirect(http.StatusFound, fmt.Sprintf("%s/error", controller.runtime.AppURL))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -171,19 +202,19 @@ func (controller *OAuthController) oauthCallbackHandler(c *gin.Context) {
|
|||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
controller.log.App.Error().Err(err).Msg("Failed to get user info from OAuth provider")
|
controller.log.App.Error().Err(err).Msg("Failed to get user info from OAuth provider")
|
||||||
c.Redirect(http.StatusTemporaryRedirect, fmt.Sprintf("%s/error", controller.runtime.AppURL))
|
c.Redirect(http.StatusFound, fmt.Sprintf("%s/error", controller.runtime.AppURL))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if user == nil {
|
if user == nil {
|
||||||
controller.log.App.Warn().Msg("OAuth provider did not return user info")
|
controller.log.App.Warn().Msg("OAuth provider did not return user info")
|
||||||
c.Redirect(http.StatusTemporaryRedirect, fmt.Sprintf("%s/error", controller.runtime.AppURL))
|
c.Redirect(http.StatusFound, fmt.Sprintf("%s/error", controller.runtime.AppURL))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if user.Email == "" {
|
if user.Email == "" {
|
||||||
controller.log.App.Warn().Msg("OAuth provider did not return an email")
|
controller.log.App.Warn().Msg("OAuth provider did not return an email")
|
||||||
c.Redirect(http.StatusTemporaryRedirect, fmt.Sprintf("%s/error", controller.runtime.AppURL))
|
c.Redirect(http.StatusFound, fmt.Sprintf("%s/error", controller.runtime.AppURL))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -191,13 +222,13 @@ func (controller *OAuthController) oauthCallbackHandler(c *gin.Context) {
|
|||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
controller.log.App.Error().Err(err).Msg("Failed to get OAuth service for session")
|
controller.log.App.Error().Err(err).Msg("Failed to get OAuth service for session")
|
||||||
c.Redirect(http.StatusTemporaryRedirect, fmt.Sprintf("%s/error", controller.runtime.AppURL))
|
c.Redirect(http.StatusFound, fmt.Sprintf("%s/error", controller.runtime.AppURL))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if svc.ID() != req.Provider {
|
if svc.ID() != req.Provider {
|
||||||
controller.log.App.Warn().Msgf("OAuth provider mismatch: expected %s, got %s", req.Provider, svc.ID())
|
controller.log.App.Warn().Msgf("OAuth provider mismatch: expected %s, got %s", req.Provider, svc.ID())
|
||||||
c.Redirect(http.StatusTemporaryRedirect, fmt.Sprintf("%s/error", controller.runtime.AppURL))
|
c.Redirect(http.StatusFound, fmt.Sprintf("%s/error", controller.runtime.AppURL))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -211,11 +242,11 @@ func (controller *OAuthController) oauthCallbackHandler(c *gin.Context) {
|
|||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
controller.log.App.Error().Err(err).Msg("Failed to encode unauthorized query")
|
controller.log.App.Error().Err(err).Msg("Failed to encode unauthorized query")
|
||||||
c.Redirect(http.StatusTemporaryRedirect, fmt.Sprintf("%s/error", controller.runtime.AppURL))
|
c.Redirect(http.StatusFound, fmt.Sprintf("%s/error", controller.runtime.AppURL))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c.Redirect(http.StatusTemporaryRedirect, fmt.Sprintf("%s/unauthorized?%s", controller.runtime.AppURL, queries.Encode()))
|
c.Redirect(http.StatusFound, fmt.Sprintf("%s/unauthorized?%s", controller.runtime.AppURL, queries.Encode()))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -260,7 +291,7 @@ func (controller *OAuthController) oauthCallbackHandler(c *gin.Context) {
|
|||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
controller.log.App.Error().Err(err).Msg("Failed to create session cookie")
|
controller.log.App.Error().Err(err).Msg("Failed to create session cookie")
|
||||||
c.Redirect(http.StatusTemporaryRedirect, fmt.Sprintf("%s/error", controller.runtime.AppURL))
|
c.Redirect(http.StatusFound, fmt.Sprintf("%s/error", controller.runtime.AppURL))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -273,10 +304,10 @@ func (controller *OAuthController) oauthCallbackHandler(c *gin.Context) {
|
|||||||
queries, err := query.Values(oauthPendingSession.CallbackParams)
|
queries, err := query.Values(oauthPendingSession.CallbackParams)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
controller.log.App.Error().Err(err).Msg("Failed to encode OIDC callback query")
|
controller.log.App.Error().Err(err).Msg("Failed to encode OIDC callback query")
|
||||||
c.Redirect(http.StatusTemporaryRedirect, fmt.Sprintf("%s/error", controller.runtime.AppURL))
|
c.Redirect(http.StatusFound, fmt.Sprintf("%s/error", controller.runtime.AppURL))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
c.Redirect(http.StatusTemporaryRedirect, fmt.Sprintf("%s/oidc/authorize?%s", controller.runtime.AppURL, queries.Encode()))
|
c.Redirect(http.StatusFound, fmt.Sprintf("%s/oidc/authorize?%s", controller.runtime.AppURL, queries.Encode()))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -288,15 +319,15 @@ func (controller *OAuthController) oauthCallbackHandler(c *gin.Context) {
|
|||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
controller.log.App.Error().Err(err).Msg("Failed to encode redirect query")
|
controller.log.App.Error().Err(err).Msg("Failed to encode redirect query")
|
||||||
c.Redirect(http.StatusTemporaryRedirect, fmt.Sprintf("%s/error", controller.runtime.AppURL))
|
c.Redirect(http.StatusFound, fmt.Sprintf("%s/error", controller.runtime.AppURL))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c.Redirect(http.StatusTemporaryRedirect, fmt.Sprintf("%s/continue?%s", controller.runtime.AppURL, queries.Encode()))
|
c.Redirect(http.StatusFound, fmt.Sprintf("%s/continue?%s", controller.runtime.AppURL, queries.Encode()))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c.Redirect(http.StatusTemporaryRedirect, controller.runtime.AppURL)
|
c.Redirect(http.StatusFound, controller.runtime.AppURL)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (controller *OAuthController) isOidcRequest(params service.OAuthCallbackParams) bool {
|
func (controller *OAuthController) isOidcRequest(params service.OAuthCallbackParams) bool {
|
||||||
|
|||||||
@@ -82,6 +82,15 @@ type AuthorizeCompleteRequest struct {
|
|||||||
Ticket string `json:"ticket" binding:"required"`
|
Ticket string `json:"ticket" binding:"required"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type AuthorizeCompleteResponse struct {
|
||||||
|
SimpleResponse
|
||||||
|
RedirectURI string `json:"redirect_uri"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type OIDCErrorResponse struct {
|
||||||
|
Error string `json:"error"`
|
||||||
|
}
|
||||||
|
|
||||||
type OIDCControllerInput struct {
|
type OIDCControllerInput struct {
|
||||||
dig.In
|
dig.In
|
||||||
|
|
||||||
@@ -114,6 +123,36 @@ func NewOIDCController(i OIDCControllerInput) *OIDCController {
|
|||||||
// This endpoint does **not** return a code, it handles param validation, ticket creation
|
// This endpoint does **not** return a code, it handles param validation, ticket creation
|
||||||
// and then redirects to the frontend to handle the consent screen. It performs no destructive
|
// and then redirects to the frontend to handle the consent screen. It performs no destructive
|
||||||
// actions (like logging out an existing session)
|
// actions (like logging out an existing session)
|
||||||
|
// Authorize godoc
|
||||||
|
//
|
||||||
|
// @Summary Authorize
|
||||||
|
// @Description OpenID Connect Authorize Endpoint
|
||||||
|
// @Accept x-www-form-urlencoded
|
||||||
|
// @Tags oidc
|
||||||
|
// @Param scope query string false "OAuth scopes (space separated, must include openid)"
|
||||||
|
// @Param response_type query string false "Response type (e.g. code)"
|
||||||
|
// @Param client_id query string false "Client ID"
|
||||||
|
// @Param redirect_uri query string false "Redirect URI"
|
||||||
|
// @Param state query string false "Opaque state value returned to the client"
|
||||||
|
// @Param nonce query string false "Nonce for ID token replay protection"
|
||||||
|
// @Param code_challenge query string false "PKCE code challenge"
|
||||||
|
// @Param code_challenge_method query string false "PKCE code challenge method (S256 or plain)"
|
||||||
|
// @Param prompt query string false "Prompt parameter (none, login, consent)"
|
||||||
|
// @Param max_age query string false "Max authentication age in seconds"
|
||||||
|
// @Param scope formData string false "OAuth scopes (space separated, must include openid)"
|
||||||
|
// @Param response_type formData string false "Response type (e.g. code)"
|
||||||
|
// @Param client_id formData string false "Client ID"
|
||||||
|
// @Param redirect_uri formData string false "Redirect URI"
|
||||||
|
// @Param state formData string false "Opaque state value returned to the client"
|
||||||
|
// @Param nonce formData string false "Nonce for ID token replay protection"
|
||||||
|
// @Param code_challenge formData string false "PKCE code challenge"
|
||||||
|
// @Param code_challenge_method formData string false "PKCE code challenge method (S256 or plain)"
|
||||||
|
// @Param prompt formData string false "Prompt parameter (none, login, consent)"
|
||||||
|
// @Param max_age formData string false "Max authentication age in seconds"
|
||||||
|
// @Success 302
|
||||||
|
// @Failure 302
|
||||||
|
// @Router /authorize [get]
|
||||||
|
// @Router /authorize [post]
|
||||||
func (controller *OIDCController) authorize(c *gin.Context) {
|
func (controller *OIDCController) authorize(c *gin.Context) {
|
||||||
if controller.oidc == nil {
|
if controller.oidc == nil {
|
||||||
controller.authorizeError(c, authorizeErrorParams{
|
controller.authorizeError(c, authorizeErrorParams{
|
||||||
@@ -261,6 +300,16 @@ func (controller *OIDCController) authorize(c *gin.Context) {
|
|||||||
|
|
||||||
// The actual **internal** endpoint that actually creates the code and session.
|
// The actual **internal** endpoint that actually creates the code and session.
|
||||||
// It is called by the frontend after the user has logged in and given consent.
|
// It is called by the frontend after the user has logged in and given consent.
|
||||||
|
// AuthorizeComplete godoc
|
||||||
|
//
|
||||||
|
// @Summary Authorize Complete
|
||||||
|
// @Description Internal endpoint for the completion of the OpenID Connect authorization flow
|
||||||
|
// @Tags oidc
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Success 200 {object} AuthorizeCompleteResponse
|
||||||
|
// @Failure 500
|
||||||
|
// @Router /api/oidc/authorize-complete [post]
|
||||||
func (controller *OIDCController) authorizeComplete(c *gin.Context) {
|
func (controller *OIDCController) authorizeComplete(c *gin.Context) {
|
||||||
if controller.oidc == nil {
|
if controller.oidc == nil {
|
||||||
// For this endpoint we return JSON errors since it's called
|
// For this endpoint we return JSON errors since it's called
|
||||||
@@ -361,17 +410,44 @@ func (controller *OIDCController) authorizeComplete(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c.JSON(200, gin.H{
|
c.JSON(200, AuthorizeCompleteResponse{
|
||||||
"status": 200,
|
SimpleResponse: SimpleResponse{
|
||||||
"redirect_uri": fmt.Sprintf("%s?%s", authorizeReq.RedirectURI, queries.Encode()),
|
Status: 200,
|
||||||
|
},
|
||||||
|
RedirectURI: fmt.Sprintf("%s?%s", authorizeReq.RedirectURI, queries.Encode()),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Token godoc
|
||||||
|
//
|
||||||
|
// @Summary Token
|
||||||
|
// @Description OpenID Connect Token Endpoint
|
||||||
|
// @Tags oidc
|
||||||
|
// @Accept x-www-form-urlencoded
|
||||||
|
// @Produce json
|
||||||
|
// @Param grant_type query string true "Grant type (authorization_code or refresh_token)"
|
||||||
|
// @Param code query string false "Authorization code (required for authorization_code grant)"
|
||||||
|
// @Param redirect_uri query string false "Redirect URI (must match the one from the authorize request)"
|
||||||
|
// @Param refresh_token query string false "Refresh token (required for refresh_token grant)"
|
||||||
|
// @Param client_id query string false "Client ID (required if not using Basic auth)"
|
||||||
|
// @Param client_secret query string false "Client secret (required for confidential clients without Basic auth)"
|
||||||
|
// @Param code_verifier query string false "PKCE code verifier (required if code_challenge was sent)"
|
||||||
|
// @Param grant_type formData string false "Grant type (authorization_code or refresh_token)"
|
||||||
|
// @Param code formData string false "Authorization code (required for authorization_code grant)"
|
||||||
|
// @Param redirect_uri formData string false "Redirect URI (must match the one from the authorize request)"
|
||||||
|
// @Param refresh_token formData string false "Refresh token (required for refresh_token grant)"
|
||||||
|
// @Param client_id formData string false "Client ID (required if not using Basic auth)"
|
||||||
|
// @Param client_secret formData string false "Client secret (required for confidential clients without Basic auth)"
|
||||||
|
// @Param code_verifier formData string false "PKCE code verifier (required if code_challenge was sent)"
|
||||||
|
// @Success 200 {object} service.TokenResponse
|
||||||
|
// @Failure 400 {object} OIDCErrorResponse
|
||||||
|
// @Failure 500 {object} OIDCErrorResponse
|
||||||
|
// @Router /oidc/token [post]
|
||||||
func (controller *OIDCController) Token(c *gin.Context) {
|
func (controller *OIDCController) Token(c *gin.Context) {
|
||||||
if controller.oidc == nil {
|
if controller.oidc == nil {
|
||||||
controller.log.App.Warn().Msg("Received OIDC request but OIDC server is not configured")
|
controller.log.App.Warn().Msg("Received OIDC request but OIDC server is not configured")
|
||||||
c.JSON(500, gin.H{
|
c.JSON(500, OIDCErrorResponse{
|
||||||
"error": "server_error",
|
Error: "server_error",
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -381,8 +457,8 @@ func (controller *OIDCController) Token(c *gin.Context) {
|
|||||||
err := c.Bind(&req)
|
err := c.Bind(&req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
controller.log.App.Warn().Err(err).Msg("Failed to bind token request")
|
controller.log.App.Warn().Err(err).Msg("Failed to bind token request")
|
||||||
c.JSON(400, gin.H{
|
c.JSON(400, OIDCErrorResponse{
|
||||||
"error": "invalid_request",
|
Error: "invalid_request",
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -390,8 +466,8 @@ func (controller *OIDCController) Token(c *gin.Context) {
|
|||||||
err = controller.oidc.ValidateGrantType(req.GrantType)
|
err = controller.oidc.ValidateGrantType(req.GrantType)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
controller.log.App.Warn().Err(err).Msg("Invalid grant type")
|
controller.log.App.Warn().Err(err).Msg("Invalid grant type")
|
||||||
c.JSON(400, gin.H{
|
c.JSON(400, OIDCErrorResponse{
|
||||||
"error": err.Error(),
|
Error: err.Error(),
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -411,8 +487,8 @@ func (controller *OIDCController) Token(c *gin.Context) {
|
|||||||
if !ok {
|
if !ok {
|
||||||
controller.log.App.Warn().Msg("Client credentials not found in basic auth")
|
controller.log.App.Warn().Msg("Client credentials not found in basic auth")
|
||||||
c.Header("www-authenticate", `Basic realm="Tinyauth OIDC Token Endpoint"`)
|
c.Header("www-authenticate", `Basic realm="Tinyauth OIDC Token Endpoint"`)
|
||||||
c.JSON(400, gin.H{
|
c.JSON(400, OIDCErrorResponse{
|
||||||
"error": "invalid_client",
|
Error: "invalid_client",
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -427,16 +503,16 @@ func (controller *OIDCController) Token(c *gin.Context) {
|
|||||||
|
|
||||||
if !ok {
|
if !ok {
|
||||||
controller.log.App.Warn().Str("clientId", creds.ClientID).Msg("Client not found")
|
controller.log.App.Warn().Str("clientId", creds.ClientID).Msg("Client not found")
|
||||||
c.JSON(400, gin.H{
|
c.JSON(400, OIDCErrorResponse{
|
||||||
"error": "invalid_client",
|
Error: "invalid_client",
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if client.ClientSecret != creds.ClientSecret {
|
if client.ClientSecret != creds.ClientSecret {
|
||||||
controller.log.App.Warn().Str("clientId", creds.ClientID).Msg("Invalid client secret")
|
controller.log.App.Warn().Str("clientId", creds.ClientID).Msg("Invalid client secret")
|
||||||
c.JSON(400, gin.H{
|
c.JSON(400, OIDCErrorResponse{
|
||||||
"error": "invalid_client",
|
Error: "invalid_client",
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -457,15 +533,15 @@ func (controller *OIDCController) Token(c *gin.Context) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
controller.log.App.Error().Err(err).Msg("Failed to delete session for reused code")
|
controller.log.App.Error().Err(err).Msg("Failed to delete session for reused code")
|
||||||
}
|
}
|
||||||
c.JSON(400, gin.H{
|
c.JSON(400, OIDCErrorResponse{
|
||||||
"error": "invalid_grant",
|
Error: "invalid_grant",
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
controller.log.App.Warn().Msg("Code not found")
|
controller.log.App.Warn().Msg("Code not found")
|
||||||
c.JSON(400, gin.H{
|
c.JSON(400, OIDCErrorResponse{
|
||||||
"error": "invalid_grant",
|
Error: "invalid_grant",
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -475,8 +551,8 @@ func (controller *OIDCController) Token(c *gin.Context) {
|
|||||||
|
|
||||||
if entry.RedirectURI != req.RedirectURI {
|
if entry.RedirectURI != req.RedirectURI {
|
||||||
controller.log.App.Warn().Msg("Redirect URI does not match")
|
controller.log.App.Warn().Msg("Redirect URI does not match")
|
||||||
c.JSON(400, gin.H{
|
c.JSON(400, OIDCErrorResponse{
|
||||||
"error": "invalid_grant",
|
Error: "invalid_grant",
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -485,8 +561,8 @@ func (controller *OIDCController) Token(c *gin.Context) {
|
|||||||
|
|
||||||
if !ok {
|
if !ok {
|
||||||
controller.log.App.Warn().Msg("PKCE validation failed")
|
controller.log.App.Warn().Msg("PKCE validation failed")
|
||||||
c.JSON(400, gin.H{
|
c.JSON(400, OIDCErrorResponse{
|
||||||
"error": "invalid_grant",
|
Error: "invalid_grant",
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -495,8 +571,8 @@ func (controller *OIDCController) Token(c *gin.Context) {
|
|||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
controller.log.App.Error().Err(err).Msg("Failed to generate access token")
|
controller.log.App.Error().Err(err).Msg("Failed to generate access token")
|
||||||
c.JSON(400, gin.H{
|
c.JSON(400, OIDCErrorResponse{
|
||||||
"error": "server_error",
|
Error: "server_error",
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -508,23 +584,23 @@ func (controller *OIDCController) Token(c *gin.Context) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, service.ErrTokenExpired) {
|
if errors.Is(err, service.ErrTokenExpired) {
|
||||||
controller.log.App.Warn().Msg("Refresh token expired")
|
controller.log.App.Warn().Msg("Refresh token expired")
|
||||||
c.JSON(400, gin.H{
|
c.JSON(400, OIDCErrorResponse{
|
||||||
"error": "invalid_grant",
|
Error: "invalid_grant",
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if errors.Is(err, service.ErrInvalidClient) {
|
if errors.Is(err, service.ErrInvalidClient) {
|
||||||
controller.log.App.Warn().Msg("Refresh token does not belong to client")
|
controller.log.App.Warn().Msg("Refresh token does not belong to client")
|
||||||
c.JSON(400, gin.H{
|
c.JSON(400, OIDCErrorResponse{
|
||||||
"error": "invalid_grant",
|
Error: "invalid_grant",
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
controller.log.App.Error().Err(err).Msg("Failed to refresh access token")
|
controller.log.App.Error().Err(err).Msg("Failed to refresh access token")
|
||||||
c.JSON(400, gin.H{
|
c.JSON(400, OIDCErrorResponse{
|
||||||
"error": "server_error",
|
Error: "server_error",
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -538,11 +614,25 @@ func (controller *OIDCController) Token(c *gin.Context) {
|
|||||||
c.JSON(200, tokenResponse)
|
c.JSON(200, tokenResponse)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Userinfo godoc
|
||||||
|
//
|
||||||
|
// @Summary Userinfo
|
||||||
|
// @Description OpenID Connect Userinfo Endpoint
|
||||||
|
// @Accept x-www-form-urlencoded
|
||||||
|
// @Tags oidc
|
||||||
|
// @Param access_token formData string false "OpenID Connect Access Token"
|
||||||
|
// @Produce json
|
||||||
|
// @Success 200 {object} service.UserinfoResponse
|
||||||
|
// @Failure 400 {object} OIDCErrorResponse
|
||||||
|
// @Failure 401 {object} OIDCErrorResponse
|
||||||
|
// @Failure 500 {object} OIDCErrorResponse
|
||||||
|
// @Router /oidc/userinfo [get]
|
||||||
|
// @Router /oidc/userinfo [post]
|
||||||
func (controller *OIDCController) Userinfo(c *gin.Context) {
|
func (controller *OIDCController) Userinfo(c *gin.Context) {
|
||||||
if controller.oidc == nil {
|
if controller.oidc == nil {
|
||||||
controller.log.App.Warn().Msg("Received OIDC userinfo request but OIDC server is not configured")
|
controller.log.App.Warn().Msg("Received OIDC userinfo request but OIDC server is not configured")
|
||||||
c.JSON(500, gin.H{
|
c.JSON(500, OIDCErrorResponse{
|
||||||
"error": "server_error",
|
Error: "server_error",
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -554,16 +644,16 @@ func (controller *OIDCController) Userinfo(c *gin.Context) {
|
|||||||
tokenType, bearerToken, ok := strings.Cut(authorization, " ")
|
tokenType, bearerToken, ok := strings.Cut(authorization, " ")
|
||||||
if !ok {
|
if !ok {
|
||||||
controller.log.App.Warn().Msg("OIDC userinfo accessed with invalid authorization header")
|
controller.log.App.Warn().Msg("OIDC userinfo accessed with invalid authorization header")
|
||||||
c.JSON(401, gin.H{
|
c.JSON(401, OIDCErrorResponse{
|
||||||
"error": "invalid_request",
|
Error: "invalid_request",
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if strings.ToLower(tokenType) != "bearer" {
|
if strings.ToLower(tokenType) != "bearer" {
|
||||||
controller.log.App.Warn().Msg("OIDC userinfo accessed with non-bearer token")
|
controller.log.App.Warn().Msg("OIDC userinfo accessed with non-bearer token")
|
||||||
c.JSON(401, gin.H{
|
c.JSON(401, OIDCErrorResponse{
|
||||||
"error": "invalid_request",
|
Error: "invalid_request",
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -572,23 +662,23 @@ func (controller *OIDCController) Userinfo(c *gin.Context) {
|
|||||||
} else if c.Request.Method == http.MethodPost {
|
} else if c.Request.Method == http.MethodPost {
|
||||||
if c.ContentType() != "application/x-www-form-urlencoded" {
|
if c.ContentType() != "application/x-www-form-urlencoded" {
|
||||||
controller.log.App.Warn().Msg("OIDC userinfo POST accessed with invalid content type")
|
controller.log.App.Warn().Msg("OIDC userinfo POST accessed with invalid content type")
|
||||||
c.JSON(400, gin.H{
|
c.JSON(400, OIDCErrorResponse{
|
||||||
"error": "invalid_request",
|
Error: "invalid_request",
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
token = c.PostForm("access_token")
|
token = c.PostForm("access_token")
|
||||||
if token == "" {
|
if token == "" {
|
||||||
controller.log.App.Warn().Msg("OIDC userinfo POST accessed without access_token")
|
controller.log.App.Warn().Msg("OIDC userinfo POST accessed without access_token")
|
||||||
c.JSON(401, gin.H{
|
c.JSON(401, OIDCErrorResponse{
|
||||||
"error": "invalid_request",
|
Error: "invalid_request",
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
controller.log.App.Warn().Msg("OIDC userinfo accessed without authorization header or POST body")
|
controller.log.App.Warn().Msg("OIDC userinfo accessed without authorization header or POST body")
|
||||||
c.JSON(401, gin.H{
|
c.JSON(401, OIDCErrorResponse{
|
||||||
"error": "invalid_request",
|
Error: "invalid_request",
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -598,15 +688,15 @@ func (controller *OIDCController) Userinfo(c *gin.Context) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, service.ErrTokenNotFound) {
|
if errors.Is(err, service.ErrTokenNotFound) {
|
||||||
controller.log.App.Warn().Msg("OIDC userinfo accessed with invalid token")
|
controller.log.App.Warn().Msg("OIDC userinfo accessed with invalid token")
|
||||||
c.JSON(401, gin.H{
|
c.JSON(401, OIDCErrorResponse{
|
||||||
"error": "invalid_grant",
|
Error: "invalid_grant",
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
controller.log.App.Error().Err(err).Msg("Failed to get access token")
|
controller.log.App.Error().Err(err).Msg("Failed to get access token")
|
||||||
c.JSON(401, gin.H{
|
c.JSON(401, OIDCErrorResponse{
|
||||||
"error": "server_error",
|
Error: "server_error",
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -614,8 +704,8 @@ func (controller *OIDCController) Userinfo(c *gin.Context) {
|
|||||||
// If we don't have the openid scope, return an error
|
// If we don't have the openid scope, return an error
|
||||||
if !slices.Contains(strings.Split(entry.Scope, " "), "openid") {
|
if !slices.Contains(strings.Split(entry.Scope, " "), "openid") {
|
||||||
controller.log.App.Warn().Msg("OIDC userinfo accessed with missing openid scope")
|
controller.log.App.Warn().Msg("OIDC userinfo accessed with missing openid scope")
|
||||||
c.JSON(401, gin.H{
|
c.JSON(401, OIDCErrorResponse{
|
||||||
"error": "invalid_scope",
|
Error: "invalid_scope",
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -626,8 +716,8 @@ func (controller *OIDCController) Userinfo(c *gin.Context) {
|
|||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
controller.log.App.Error().Err(err).Msg("Failed to get user info")
|
controller.log.App.Error().Err(err).Msg("Failed to get user info")
|
||||||
c.JSON(401, gin.H{
|
c.JSON(401, OIDCErrorResponse{
|
||||||
"error": "server_error",
|
Error: "server_error",
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -662,9 +752,11 @@ func (controller *OIDCController) authorizeError(c *gin.Context, params authoriz
|
|||||||
redirectUrl := fmt.Sprintf("%s?%s", params.callback, queries.Encode())
|
redirectUrl := fmt.Sprintf("%s?%s", params.callback, queries.Encode())
|
||||||
|
|
||||||
if params.json {
|
if params.json {
|
||||||
c.JSON(200, gin.H{
|
c.JSON(200, AuthorizeCompleteResponse{
|
||||||
"status": 200,
|
SimpleResponse: SimpleResponse{
|
||||||
"redirect_uri": redirectUrl,
|
Status: 200,
|
||||||
|
},
|
||||||
|
RedirectURI: redirectUrl,
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -694,9 +786,11 @@ func (controller *OIDCController) authorizeError(c *gin.Context, params authoriz
|
|||||||
}
|
}
|
||||||
|
|
||||||
if params.json {
|
if params.json {
|
||||||
c.JSON(200, gin.H{
|
c.JSON(200, AuthorizeCompleteResponse{
|
||||||
"status": 200,
|
SimpleResponse: SimpleResponse{
|
||||||
"redirect_uri": redirectUrl,
|
Status: 200,
|
||||||
|
},
|
||||||
|
RedirectURI: redirectUrl,
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
+1032
-2
File diff suppressed because it is too large
Load Diff
+1032
-2
File diff suppressed because it is too large
Load Diff
@@ -1,4 +1,213 @@
|
|||||||
basePath: /api
|
basePath: /
|
||||||
|
definitions:
|
||||||
|
controller.ACRApp:
|
||||||
|
properties:
|
||||||
|
appUrl:
|
||||||
|
type: string
|
||||||
|
cookieDomain:
|
||||||
|
type: string
|
||||||
|
subdomainsEnabled:
|
||||||
|
type: boolean
|
||||||
|
type: object
|
||||||
|
controller.ACRAuth:
|
||||||
|
properties:
|
||||||
|
providers:
|
||||||
|
items:
|
||||||
|
$ref: '#/definitions/model.Provider'
|
||||||
|
type: array
|
||||||
|
type: object
|
||||||
|
controller.ACROAuth:
|
||||||
|
properties:
|
||||||
|
autoRedirect:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
controller.ACRUI:
|
||||||
|
properties:
|
||||||
|
backgroundImage:
|
||||||
|
type: string
|
||||||
|
forgotPasswordMessage:
|
||||||
|
type: string
|
||||||
|
title:
|
||||||
|
type: string
|
||||||
|
warningsEnabled:
|
||||||
|
type: boolean
|
||||||
|
type: object
|
||||||
|
controller.AppContextResponse:
|
||||||
|
properties:
|
||||||
|
app:
|
||||||
|
$ref: '#/definitions/controller.ACRApp'
|
||||||
|
auth:
|
||||||
|
$ref: '#/definitions/controller.ACRAuth'
|
||||||
|
message:
|
||||||
|
type: string
|
||||||
|
oauth:
|
||||||
|
$ref: '#/definitions/controller.ACROAuth'
|
||||||
|
status:
|
||||||
|
type: integer
|
||||||
|
ui:
|
||||||
|
$ref: '#/definitions/controller.ACRUI'
|
||||||
|
type: object
|
||||||
|
controller.AuthorizeCompleteResponse:
|
||||||
|
properties:
|
||||||
|
message:
|
||||||
|
type: string
|
||||||
|
redirect_uri:
|
||||||
|
type: string
|
||||||
|
status:
|
||||||
|
type: integer
|
||||||
|
type: object
|
||||||
|
controller.OAuthURLSuccessResponse:
|
||||||
|
properties:
|
||||||
|
message:
|
||||||
|
type: string
|
||||||
|
status:
|
||||||
|
type: integer
|
||||||
|
url:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
controller.OIDCErrorResponse:
|
||||||
|
properties:
|
||||||
|
error:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
controller.SimpleResponse:
|
||||||
|
properties:
|
||||||
|
message:
|
||||||
|
type: string
|
||||||
|
status:
|
||||||
|
type: integer
|
||||||
|
type: object
|
||||||
|
controller.UCRAuth:
|
||||||
|
properties:
|
||||||
|
authenticated:
|
||||||
|
type: boolean
|
||||||
|
email:
|
||||||
|
type: string
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
providerId:
|
||||||
|
type: string
|
||||||
|
username:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
controller.UCROAuth:
|
||||||
|
properties:
|
||||||
|
active:
|
||||||
|
type: boolean
|
||||||
|
displayName:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
controller.UCRTOTP:
|
||||||
|
properties:
|
||||||
|
pending:
|
||||||
|
type: boolean
|
||||||
|
type: object
|
||||||
|
controller.UCRTailscale:
|
||||||
|
properties:
|
||||||
|
nodeName:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
controller.UserContextResponse:
|
||||||
|
properties:
|
||||||
|
auth:
|
||||||
|
$ref: '#/definitions/controller.UCRAuth'
|
||||||
|
message:
|
||||||
|
type: string
|
||||||
|
oauth:
|
||||||
|
$ref: '#/definitions/controller.UCROAuth'
|
||||||
|
status:
|
||||||
|
type: integer
|
||||||
|
tailscale:
|
||||||
|
$ref: '#/definitions/controller.UCRTailscale'
|
||||||
|
totp:
|
||||||
|
$ref: '#/definitions/controller.UCRTOTP'
|
||||||
|
type: object
|
||||||
|
model.AddressClaim:
|
||||||
|
properties:
|
||||||
|
country:
|
||||||
|
type: string
|
||||||
|
formatted:
|
||||||
|
type: string
|
||||||
|
locality:
|
||||||
|
type: string
|
||||||
|
postal_code:
|
||||||
|
type: string
|
||||||
|
region:
|
||||||
|
type: string
|
||||||
|
street_address:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
model.Provider:
|
||||||
|
properties:
|
||||||
|
id:
|
||||||
|
type: string
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
oauth:
|
||||||
|
type: boolean
|
||||||
|
type: object
|
||||||
|
service.TokenResponse:
|
||||||
|
properties:
|
||||||
|
access_token:
|
||||||
|
type: string
|
||||||
|
expires_in:
|
||||||
|
type: integer
|
||||||
|
id_token:
|
||||||
|
type: string
|
||||||
|
refresh_token:
|
||||||
|
type: string
|
||||||
|
scope:
|
||||||
|
type: string
|
||||||
|
token_type:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
service.UserinfoResponse:
|
||||||
|
properties:
|
||||||
|
address:
|
||||||
|
$ref: '#/definitions/model.AddressClaim'
|
||||||
|
birthdate:
|
||||||
|
type: string
|
||||||
|
email:
|
||||||
|
type: string
|
||||||
|
email_verified:
|
||||||
|
type: boolean
|
||||||
|
family_name:
|
||||||
|
type: string
|
||||||
|
gender:
|
||||||
|
type: string
|
||||||
|
given_name:
|
||||||
|
type: string
|
||||||
|
groups:
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
type: array
|
||||||
|
locale:
|
||||||
|
type: string
|
||||||
|
middle_name:
|
||||||
|
type: string
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
nickname:
|
||||||
|
type: string
|
||||||
|
phone_number:
|
||||||
|
type: string
|
||||||
|
phone_number_verified:
|
||||||
|
type: boolean
|
||||||
|
picture:
|
||||||
|
type: string
|
||||||
|
preferred_username:
|
||||||
|
type: string
|
||||||
|
profile:
|
||||||
|
type: string
|
||||||
|
sub:
|
||||||
|
type: string
|
||||||
|
updated_at:
|
||||||
|
type: integer
|
||||||
|
website:
|
||||||
|
type: string
|
||||||
|
zoneinfo:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
info:
|
info:
|
||||||
contact: {}
|
contact: {}
|
||||||
description: Swagger documentation for Tinyauth's API.
|
description: Swagger documentation for Tinyauth's API.
|
||||||
@@ -7,5 +216,475 @@ info:
|
|||||||
url: https://github.com/tinyauthapp/tinyauth/blob/main/LICENSE
|
url: https://github.com/tinyauthapp/tinyauth/blob/main/LICENSE
|
||||||
title: Tinyauth API
|
title: Tinyauth API
|
||||||
version: development
|
version: development
|
||||||
paths: {}
|
paths:
|
||||||
|
/api/context/app:
|
||||||
|
get:
|
||||||
|
description: Get the app context
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/controller.AppContextResponse'
|
||||||
|
summary: App context
|
||||||
|
tags:
|
||||||
|
- context
|
||||||
|
/api/context/user:
|
||||||
|
get:
|
||||||
|
description: Get the user context
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/controller.UserContextResponse'
|
||||||
|
summary: User context
|
||||||
|
tags:
|
||||||
|
- context
|
||||||
|
/api/healthz:
|
||||||
|
get:
|
||||||
|
description: Check if the server is up and running
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/controller.SimpleResponse'
|
||||||
|
summary: Healthcheck
|
||||||
|
tags:
|
||||||
|
- health
|
||||||
|
head:
|
||||||
|
description: Check if the server is up and running
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/controller.SimpleResponse'
|
||||||
|
summary: Healthcheck
|
||||||
|
tags:
|
||||||
|
- health
|
||||||
|
/api/oauth/callback/{id}:
|
||||||
|
get:
|
||||||
|
description: Callback URL for OAuth providers
|
||||||
|
parameters:
|
||||||
|
- description: Provider ID
|
||||||
|
in: path
|
||||||
|
name: id
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
- description: State
|
||||||
|
in: query
|
||||||
|
name: code
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
- description: Code
|
||||||
|
in: query
|
||||||
|
name: state
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
responses:
|
||||||
|
"302":
|
||||||
|
description: Found
|
||||||
|
summary: OAuth Callback
|
||||||
|
tags:
|
||||||
|
- oauth
|
||||||
|
/api/oauth/url/{id}:
|
||||||
|
get:
|
||||||
|
description: Get an OAuth URL for the specified provider
|
||||||
|
parameters:
|
||||||
|
- description: Provider ID
|
||||||
|
in: path
|
||||||
|
name: id
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
- description: Login for
|
||||||
|
in: query
|
||||||
|
name: login_for
|
||||||
|
type: string
|
||||||
|
- description: OpenID Connect Ticket
|
||||||
|
in: query
|
||||||
|
name: oidc_ticket
|
||||||
|
type: string
|
||||||
|
- description: OpenID Connect Scope
|
||||||
|
in: query
|
||||||
|
name: oidc_scope
|
||||||
|
type: string
|
||||||
|
- description: OpenID Connect Name
|
||||||
|
in: query
|
||||||
|
name: oidc_name
|
||||||
|
type: string
|
||||||
|
- description: Redirect URI
|
||||||
|
in: query
|
||||||
|
name: redirect_uri
|
||||||
|
type: string
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/controller.OAuthURLSuccessResponse'
|
||||||
|
"400":
|
||||||
|
description: Bad Request
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/controller.SimpleResponse'
|
||||||
|
"500":
|
||||||
|
description: Internal Server Error
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/controller.SimpleResponse'
|
||||||
|
summary: OAuth URL
|
||||||
|
tags:
|
||||||
|
- oauth
|
||||||
|
/api/oidc/authorize-complete:
|
||||||
|
post:
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
|
description: Internal endpoint for the completion of the OpenID Connect authorization
|
||||||
|
flow
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/controller.AuthorizeCompleteResponse'
|
||||||
|
"500":
|
||||||
|
description: Internal Server Error
|
||||||
|
summary: Authorize Complete
|
||||||
|
tags:
|
||||||
|
- oidc
|
||||||
|
/authorize:
|
||||||
|
get:
|
||||||
|
consumes:
|
||||||
|
- application/x-www-form-urlencoded
|
||||||
|
description: OpenID Connect Authorize Endpoint
|
||||||
|
parameters:
|
||||||
|
- description: OAuth scopes (space separated, must include openid)
|
||||||
|
in: query
|
||||||
|
name: scope
|
||||||
|
type: string
|
||||||
|
- description: Response type (e.g. code)
|
||||||
|
in: query
|
||||||
|
name: response_type
|
||||||
|
type: string
|
||||||
|
- description: Client ID
|
||||||
|
in: query
|
||||||
|
name: client_id
|
||||||
|
type: string
|
||||||
|
- description: Redirect URI
|
||||||
|
in: query
|
||||||
|
name: redirect_uri
|
||||||
|
type: string
|
||||||
|
- description: Opaque state value returned to the client
|
||||||
|
in: query
|
||||||
|
name: state
|
||||||
|
type: string
|
||||||
|
- description: Nonce for ID token replay protection
|
||||||
|
in: query
|
||||||
|
name: nonce
|
||||||
|
type: string
|
||||||
|
- description: PKCE code challenge
|
||||||
|
in: query
|
||||||
|
name: code_challenge
|
||||||
|
type: string
|
||||||
|
- description: PKCE code challenge method (S256 or plain)
|
||||||
|
in: query
|
||||||
|
name: code_challenge_method
|
||||||
|
type: string
|
||||||
|
- description: Prompt parameter (none, login, consent)
|
||||||
|
in: query
|
||||||
|
name: prompt
|
||||||
|
type: string
|
||||||
|
- description: Max authentication age in seconds
|
||||||
|
in: query
|
||||||
|
name: max_age
|
||||||
|
type: string
|
||||||
|
- description: OAuth scopes (space separated, must include openid)
|
||||||
|
in: formData
|
||||||
|
name: scope
|
||||||
|
type: string
|
||||||
|
- description: Response type (e.g. code)
|
||||||
|
in: formData
|
||||||
|
name: response_type
|
||||||
|
type: string
|
||||||
|
- description: Client ID
|
||||||
|
in: formData
|
||||||
|
name: client_id
|
||||||
|
type: string
|
||||||
|
- description: Redirect URI
|
||||||
|
in: formData
|
||||||
|
name: redirect_uri
|
||||||
|
type: string
|
||||||
|
- description: Opaque state value returned to the client
|
||||||
|
in: formData
|
||||||
|
name: state
|
||||||
|
type: string
|
||||||
|
- description: Nonce for ID token replay protection
|
||||||
|
in: formData
|
||||||
|
name: nonce
|
||||||
|
type: string
|
||||||
|
- description: PKCE code challenge
|
||||||
|
in: formData
|
||||||
|
name: code_challenge
|
||||||
|
type: string
|
||||||
|
- description: PKCE code challenge method (S256 or plain)
|
||||||
|
in: formData
|
||||||
|
name: code_challenge_method
|
||||||
|
type: string
|
||||||
|
- description: Prompt parameter (none, login, consent)
|
||||||
|
in: formData
|
||||||
|
name: prompt
|
||||||
|
type: string
|
||||||
|
- description: Max authentication age in seconds
|
||||||
|
in: formData
|
||||||
|
name: max_age
|
||||||
|
type: string
|
||||||
|
responses:
|
||||||
|
"302":
|
||||||
|
description: Found
|
||||||
|
summary: Authorize
|
||||||
|
tags:
|
||||||
|
- oidc
|
||||||
|
post:
|
||||||
|
consumes:
|
||||||
|
- application/x-www-form-urlencoded
|
||||||
|
description: OpenID Connect Authorize Endpoint
|
||||||
|
parameters:
|
||||||
|
- description: OAuth scopes (space separated, must include openid)
|
||||||
|
in: query
|
||||||
|
name: scope
|
||||||
|
type: string
|
||||||
|
- description: Response type (e.g. code)
|
||||||
|
in: query
|
||||||
|
name: response_type
|
||||||
|
type: string
|
||||||
|
- description: Client ID
|
||||||
|
in: query
|
||||||
|
name: client_id
|
||||||
|
type: string
|
||||||
|
- description: Redirect URI
|
||||||
|
in: query
|
||||||
|
name: redirect_uri
|
||||||
|
type: string
|
||||||
|
- description: Opaque state value returned to the client
|
||||||
|
in: query
|
||||||
|
name: state
|
||||||
|
type: string
|
||||||
|
- description: Nonce for ID token replay protection
|
||||||
|
in: query
|
||||||
|
name: nonce
|
||||||
|
type: string
|
||||||
|
- description: PKCE code challenge
|
||||||
|
in: query
|
||||||
|
name: code_challenge
|
||||||
|
type: string
|
||||||
|
- description: PKCE code challenge method (S256 or plain)
|
||||||
|
in: query
|
||||||
|
name: code_challenge_method
|
||||||
|
type: string
|
||||||
|
- description: Prompt parameter (none, login, consent)
|
||||||
|
in: query
|
||||||
|
name: prompt
|
||||||
|
type: string
|
||||||
|
- description: Max authentication age in seconds
|
||||||
|
in: query
|
||||||
|
name: max_age
|
||||||
|
type: string
|
||||||
|
- description: OAuth scopes (space separated, must include openid)
|
||||||
|
in: formData
|
||||||
|
name: scope
|
||||||
|
type: string
|
||||||
|
- description: Response type (e.g. code)
|
||||||
|
in: formData
|
||||||
|
name: response_type
|
||||||
|
type: string
|
||||||
|
- description: Client ID
|
||||||
|
in: formData
|
||||||
|
name: client_id
|
||||||
|
type: string
|
||||||
|
- description: Redirect URI
|
||||||
|
in: formData
|
||||||
|
name: redirect_uri
|
||||||
|
type: string
|
||||||
|
- description: Opaque state value returned to the client
|
||||||
|
in: formData
|
||||||
|
name: state
|
||||||
|
type: string
|
||||||
|
- description: Nonce for ID token replay protection
|
||||||
|
in: formData
|
||||||
|
name: nonce
|
||||||
|
type: string
|
||||||
|
- description: PKCE code challenge
|
||||||
|
in: formData
|
||||||
|
name: code_challenge
|
||||||
|
type: string
|
||||||
|
- description: PKCE code challenge method (S256 or plain)
|
||||||
|
in: formData
|
||||||
|
name: code_challenge_method
|
||||||
|
type: string
|
||||||
|
- description: Prompt parameter (none, login, consent)
|
||||||
|
in: formData
|
||||||
|
name: prompt
|
||||||
|
type: string
|
||||||
|
- description: Max authentication age in seconds
|
||||||
|
in: formData
|
||||||
|
name: max_age
|
||||||
|
type: string
|
||||||
|
responses:
|
||||||
|
"302":
|
||||||
|
description: Found
|
||||||
|
summary: Authorize
|
||||||
|
tags:
|
||||||
|
- oidc
|
||||||
|
/oidc/token:
|
||||||
|
post:
|
||||||
|
consumes:
|
||||||
|
- application/x-www-form-urlencoded
|
||||||
|
description: OpenID Connect Token Endpoint
|
||||||
|
parameters:
|
||||||
|
- description: Grant type (authorization_code or refresh_token)
|
||||||
|
in: query
|
||||||
|
name: grant_type
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
- description: Authorization code (required for authorization_code grant)
|
||||||
|
in: query
|
||||||
|
name: code
|
||||||
|
type: string
|
||||||
|
- description: Redirect URI (must match the one from the authorize request)
|
||||||
|
in: query
|
||||||
|
name: redirect_uri
|
||||||
|
type: string
|
||||||
|
- description: Refresh token (required for refresh_token grant)
|
||||||
|
in: query
|
||||||
|
name: refresh_token
|
||||||
|
type: string
|
||||||
|
- description: Client ID (required if not using Basic auth)
|
||||||
|
in: query
|
||||||
|
name: client_id
|
||||||
|
type: string
|
||||||
|
- description: Client secret (required for confidential clients without Basic
|
||||||
|
auth)
|
||||||
|
in: query
|
||||||
|
name: client_secret
|
||||||
|
type: string
|
||||||
|
- description: PKCE code verifier (required if code_challenge was sent)
|
||||||
|
in: query
|
||||||
|
name: code_verifier
|
||||||
|
type: string
|
||||||
|
- description: Grant type (authorization_code or refresh_token)
|
||||||
|
in: formData
|
||||||
|
name: grant_type
|
||||||
|
type: string
|
||||||
|
- description: Authorization code (required for authorization_code grant)
|
||||||
|
in: formData
|
||||||
|
name: code
|
||||||
|
type: string
|
||||||
|
- description: Redirect URI (must match the one from the authorize request)
|
||||||
|
in: formData
|
||||||
|
name: redirect_uri
|
||||||
|
type: string
|
||||||
|
- description: Refresh token (required for refresh_token grant)
|
||||||
|
in: formData
|
||||||
|
name: refresh_token
|
||||||
|
type: string
|
||||||
|
- description: Client ID (required if not using Basic auth)
|
||||||
|
in: formData
|
||||||
|
name: client_id
|
||||||
|
type: string
|
||||||
|
- description: Client secret (required for confidential clients without Basic
|
||||||
|
auth)
|
||||||
|
in: formData
|
||||||
|
name: client_secret
|
||||||
|
type: string
|
||||||
|
- description: PKCE code verifier (required if code_challenge was sent)
|
||||||
|
in: formData
|
||||||
|
name: code_verifier
|
||||||
|
type: string
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/service.TokenResponse'
|
||||||
|
"400":
|
||||||
|
description: Bad Request
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/controller.OIDCErrorResponse'
|
||||||
|
"500":
|
||||||
|
description: Internal Server Error
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/controller.OIDCErrorResponse'
|
||||||
|
summary: Token
|
||||||
|
tags:
|
||||||
|
- oidc
|
||||||
|
/oidc/userinfo:
|
||||||
|
get:
|
||||||
|
consumes:
|
||||||
|
- application/x-www-form-urlencoded
|
||||||
|
description: OpenID Connect Userinfo Endpoint
|
||||||
|
parameters:
|
||||||
|
- description: OpenID Connect Access Token
|
||||||
|
in: formData
|
||||||
|
name: access_token
|
||||||
|
type: string
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/service.UserinfoResponse'
|
||||||
|
"400":
|
||||||
|
description: Bad Request
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/controller.OIDCErrorResponse'
|
||||||
|
"401":
|
||||||
|
description: Unauthorized
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/controller.OIDCErrorResponse'
|
||||||
|
"500":
|
||||||
|
description: Internal Server Error
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/controller.OIDCErrorResponse'
|
||||||
|
summary: Userinfo
|
||||||
|
tags:
|
||||||
|
- oidc
|
||||||
|
post:
|
||||||
|
consumes:
|
||||||
|
- application/x-www-form-urlencoded
|
||||||
|
description: OpenID Connect Userinfo Endpoint
|
||||||
|
parameters:
|
||||||
|
- description: OpenID Connect Access Token
|
||||||
|
in: formData
|
||||||
|
name: access_token
|
||||||
|
type: string
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/service.UserinfoResponse'
|
||||||
|
"400":
|
||||||
|
description: Bad Request
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/controller.OIDCErrorResponse'
|
||||||
|
"401":
|
||||||
|
description: Unauthorized
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/controller.OIDCErrorResponse'
|
||||||
|
"500":
|
||||||
|
description: Internal Server Error
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/controller.OIDCErrorResponse'
|
||||||
|
summary: Userinfo
|
||||||
|
tags:
|
||||||
|
- oidc
|
||||||
swagger: "2.0"
|
swagger: "2.0"
|
||||||
|
|||||||
Reference in New Issue
Block a user