photoprism/internal/api/auth_header.go
Michael Mayer 467f7b1585 OAuth2: Add Client Credentials Authentication #213 #782 #808 #3730 #3943
This adds standard OAuth2 client credentials and bearer token support as
well as scope-based authorization checks for REST API clients. Note that
this initial implementation should not be used in production and that
the access token limit has not been implemented yet.

Signed-off-by: Michael Mayer <michael@photoprism.app>
2023-12-12 18:42:50 +01:00

81 lines
2.2 KiB
Go

package api
import (
"crypto/sha1"
"encoding/base64"
"fmt"
"strings"
"github.com/gin-gonic/gin"
"github.com/photoprism/photoprism/internal/server/header"
"github.com/photoprism/photoprism/pkg/clean"
)
// SessionID returns the session ID from the request context,
// or an empty string if there is none.
func SessionID(c *gin.Context) string {
// Default is an empty string if no context or ID is set.
if c == nil {
return ""
}
// First check the X-Session-ID header for an existing ID.
if id := clean.ID(c.GetHeader(header.SessionID)); id != "" {
return id
}
// Otherwise, return the bearer token, if any.
return BearerToken(c)
}
// BearerToken returns the value of the bearer token header, or an empty string if there is none.
func BearerToken(c *gin.Context) string {
if authType, bearerToken := Authorization(c); authType == "Bearer" && bearerToken != "" {
return bearerToken
}
return ""
}
// Authorization returns the authentication type and token from the authorization request header,
// or an empty string if there is none.
func Authorization(c *gin.Context) (authType, authToken string) {
if s := c.GetHeader(header.Authorization); s == "" {
// Ignore.
} else if t := strings.Split(s, " "); len(t) != 2 {
// Ignore.
} else {
return clean.ID(t[0]), clean.ID(t[1])
}
return "", ""
}
// BasicAuth checks the basic authorization header for credentials and returns them if found.
//
// Note that OAuth 2.0 defines basic authentication differently than RFC 7617, however, this
// does not matter as long as only alphanumeric characters are used for client id and secret:
// https://www.scottbrady91.com/oauth/client-authentication#:~:text=OAuth%20Basic%20Authentication
func BasicAuth(c *gin.Context) (username, password, cacheKey string) {
authType, authToken := Authorization(c)
if authType != "Basic" || authToken == "" {
return "", "", ""
}
auth, err := base64.StdEncoding.DecodeString(authToken)
if err != nil {
return "", "", ""
}
credentials := strings.SplitN(string(auth), ":", 2)
if len(credentials) != 2 {
return "", "", ""
}
cacheKey = fmt.Sprintf("%x", sha1.Sum([]byte(authToken)))
return credentials[0], credentials[1], cacheKey
}