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 // TODO
// Send response. // 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" "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 // GET /api/v1/oauth/userinfo
func OAuthRedirect(router *gin.RouterGroup) { func OAuthUserinfo(router *gin.RouterGroup) {
router.GET("/oauth/redirect", func(c *gin.Context) { router.GET("/oauth/userinfo", func(c *gin.Context) {
// Prevent CDNs from caching this endpoint. // Prevent CDNs from caching this endpoint.
if header.IsCdn(c.Request) { if header.IsCdn(c.Request) {
AbortNotFound(c) AbortNotFound(c)
@@ -29,7 +29,7 @@ func OAuthRedirect(router *gin.RouterGroup) {
// Get client IP address for logs and rate limiting checks. // Get client IP address for logs and rate limiting checks.
clientIp := ClientIP(c) clientIp := ClientIP(c)
actor := "unknown client" actor := "unknown client"
action := "redirect" action := "userinfo"
// Abort if running in public mode. // Abort if running in public mode.
if get.Config().Public() { if get.Config().Public() {
@@ -41,6 +41,6 @@ func OAuthRedirect(router *gin.RouterGroup) {
// TODO // TODO
// Send response. // 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" "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 // GET /api/v1/oidc/login
func OIDCLogin(router *gin.RouterGroup) { func OIDCLogin(router *gin.RouterGroup) {
@@ -41,6 +41,6 @@ func OIDCLogin(router *gin.RouterGroup) {
// TODO // TODO
// Send response. // 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. // Register JSON REST-API version 1 (APIv1) routes, grouped by functionality.
// Docs: https://pkg.go.dev/github.com/photoprism/photoprism/internal/api // Docs: https://pkg.go.dev/github.com/photoprism/photoprism/internal/api
// Authentication. // User Sessions.
api.CreateSession(APIv1) api.CreateSession(APIv1)
api.GetSession(APIv1) api.GetSession(APIv1)
api.DeleteSession(APIv1) api.DeleteSession(APIv1)
// OAuth2 Client Endpoints.
api.OAuthAuthorize(APIv1) api.OAuthAuthorize(APIv1)
api.OAuthRedirect(APIv1) api.OAuthUserinfo(APIv1)
api.OAuthToken(APIv1) api.OAuthToken(APIv1)
api.OAuthRevoke(APIv1) api.OAuthRevoke(APIv1)
api.OIDCLogin(APIv1)
// Server Config. // OIDC Client Endpoints.
api.OIDCLogin(APIv1)
api.OIDCRedirect(APIv1)
// Global Configuration.
api.GetConfigOptions(APIv1) api.GetConfigOptions(APIv1)
api.SaveConfigOptions(APIv1) api.SaveConfigOptions(APIv1)
api.StopServer(APIv1) api.StopServer(APIv1)
// Custom Settings. // User Settings.
api.GetClientConfig(APIv1) api.GetClientConfig(APIv1)
api.GetSettings(APIv1) api.GetSettings(APIv1)
api.SaveSettings(APIv1) api.SaveSettings(APIv1)
// Profile and Uploads. // User Profile and Uploads.
api.UploadUserFiles(APIv1) api.UploadUserFiles(APIv1)
api.ProcessUserUpload(APIv1) api.ProcessUserUpload(APIv1)
api.UploadUserAvatar(APIv1) api.UploadUserAvatar(APIv1)

View File

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

View File

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