API: Refactor OAuth2 and OIDC endpoints #782

Signed-off-by: Michael Mayer <michael@photoprism.app>
This commit is contained in:
Michael Mayer
2024-06-27 10:16:12 +02:00
parent 3c9aaf8dc1
commit 7dff5511bc
7 changed files with 69 additions and 18 deletions

View File

@@ -41,6 +41,6 @@ func OAuthAuthorize(router *gin.RouterGroup) {
// TODO
// Send response.
c.JSON(http.StatusOK, gin.H{"status": StatusSuccess})
c.JSON(http.StatusMethodNotAllowed, gin.H{"status": StatusFailed})
})
}

View File

@@ -12,11 +12,11 @@ import (
"github.com/photoprism/photoprism/pkg/i18n"
)
// OAuthRedirect creates a new access token for authenticated clients and then redirects back to the application.
// OAuthUserinfo returns information about the authenticated user.
//
// GET /api/v1/oauth/redirect
func OAuthRedirect(router *gin.RouterGroup) {
router.GET("/oauth/redirect", func(c *gin.Context) {
// GET /api/v1/oauth/userinfo
func OAuthUserinfo(router *gin.RouterGroup) {
router.GET("/oauth/userinfo", func(c *gin.Context) {
// Prevent CDNs from caching this endpoint.
if header.IsCdn(c.Request) {
AbortNotFound(c)
@@ -29,7 +29,7 @@ func OAuthRedirect(router *gin.RouterGroup) {
// Get client IP address for logs and rate limiting checks.
clientIp := ClientIP(c)
actor := "unknown client"
action := "redirect"
action := "userinfo"
// Abort if running in public mode.
if get.Config().Public() {
@@ -41,6 +41,6 @@ func OAuthRedirect(router *gin.RouterGroup) {
// TODO
// Send response.
c.JSON(http.StatusOK, gin.H{"status": StatusSuccess})
c.JSON(http.StatusMethodNotAllowed, gin.H{"status": StatusFailed})
})
}

View File

@@ -12,7 +12,7 @@ import (
"github.com/photoprism/photoprism/pkg/i18n"
)
// OIDCLogin redirects to the login page of the configured OpenID Connect provider, if any.
// OIDCLogin redirects a browser to the login page of the configured OpenID Connect provider, if any.
//
// GET /api/v1/oidc/login
func OIDCLogin(router *gin.RouterGroup) {
@@ -41,6 +41,6 @@ func OIDCLogin(router *gin.RouterGroup) {
// TODO
// Send response.
c.JSON(http.StatusOK, gin.H{"status": StatusSuccess})
c.JSON(http.StatusMethodNotAllowed, gin.H{"status": StatusFailed})
})
}

View File

@@ -0,0 +1,46 @@
package api
import (
"net/http"
"github.com/gin-gonic/gin"
"github.com/photoprism/photoprism/internal/event"
"github.com/photoprism/photoprism/internal/get"
"github.com/photoprism/photoprism/pkg/authn"
"github.com/photoprism/photoprism/pkg/header"
"github.com/photoprism/photoprism/pkg/i18n"
)
// OIDCRedirect creates a new access token for authenticated users and then redirects the browser back to the app.
//
// GET /api/v1/oidc/redirect
func OIDCRedirect(router *gin.RouterGroup) {
router.GET("/oidc/redirect", func(c *gin.Context) {
// Prevent CDNs from caching this endpoint.
if header.IsCdn(c.Request) {
AbortNotFound(c)
return
}
// Disable caching of responses.
c.Header(header.CacheControl, header.CacheControlNoStore)
// Get client IP address for logs and rate limiting checks.
clientIp := ClientIP(c)
actor := "unknown client"
action := "redirect"
// Abort if running in public mode.
if get.Config().Public() {
event.AuditErr([]string{clientIp, "oidc", actor, action, authn.ErrDisabledInPublicMode.Error()})
Abort(c, http.StatusForbidden, i18n.ErrForbidden)
return
}
// TODO
// Send response.
c.JSON(http.StatusMethodNotAllowed, gin.H{"status": StatusFailed})
})
}

View File

@@ -33,27 +33,32 @@ func registerRoutes(router *gin.Engine, conf *config.Config) {
// Register JSON REST-API version 1 (APIv1) routes, grouped by functionality.
// Docs: https://pkg.go.dev/github.com/photoprism/photoprism/internal/api
// Authentication.
// User Sessions.
api.CreateSession(APIv1)
api.GetSession(APIv1)
api.DeleteSession(APIv1)
// OAuth2 Client Endpoints.
api.OAuthAuthorize(APIv1)
api.OAuthRedirect(APIv1)
api.OAuthUserinfo(APIv1)
api.OAuthToken(APIv1)
api.OAuthRevoke(APIv1)
api.OIDCLogin(APIv1)
// Server Config.
// OIDC Client Endpoints.
api.OIDCLogin(APIv1)
api.OIDCRedirect(APIv1)
// Global Configuration.
api.GetConfigOptions(APIv1)
api.SaveConfigOptions(APIv1)
api.StopServer(APIv1)
// Custom Settings.
// User Settings.
api.GetClientConfig(APIv1)
api.GetSettings(APIv1)
api.SaveSettings(APIv1)
// Profile and Uploads.
// User Profile and Uploads.
api.UploadUserFiles(APIv1)
api.ProcessUserUpload(APIv1)
api.UploadUserAvatar(APIv1)

View File

@@ -43,7 +43,7 @@ type OAuthAuthorizationServer struct {
func NewOAuthAuthorizationServer(conf *config.Config) *OAuthAuthorizationServer {
return &OAuthAuthorizationServer{
Issuer: conf.SiteUrl(),
AuthorizationEndpoint: "",
AuthorizationEndpoint: fmt.Sprintf("%sapi/v1/oauth/authorize", conf.SiteUrl()),
TokenEndpoint: fmt.Sprintf("%sapi/v1/oauth/token", conf.SiteUrl()),
ScopesSupported: acl.Rules.Resources(),
ResponseTypesSupported: OAuthResponseTypes,

View File

@@ -39,9 +39,9 @@ type OpenIDConfiguration struct {
func NewOpenIDConfiguration(conf *config.Config) *OpenIDConfiguration {
return &OpenIDConfiguration{
Issuer: conf.SiteUrl(),
AuthorizationEndpoint: "",
AuthorizationEndpoint: fmt.Sprintf("%sapi/v1/oauth/authorize", conf.SiteUrl()),
TokenEndpoint: fmt.Sprintf("%sapi/v1/oauth/token", conf.SiteUrl()),
UserinfoEndpoint: "",
UserinfoEndpoint: fmt.Sprintf("%sapi/v1/oauth/userinfo", conf.SiteUrl()),
RegistrationEndpoint: "",
JwksUri: "",
ResponseTypesSupported: OAuthResponseTypes,