mirror of
https://github.com/pocket-id/pocket-id.git
synced 2026-09-19 17:39:05 +02:00
118 lines
4.9 KiB
Go
118 lines
4.9 KiB
Go
package webauthn
|
|
|
|
import (
|
|
"context"
|
|
"net/http"
|
|
"time"
|
|
|
|
"github.com/danielgtaylor/huma/v2"
|
|
"github.com/lestrrat-go/jwx/v3/jwt"
|
|
"gorm.io/gorm"
|
|
|
|
"github.com/pocket-id/pocket-id/backend/internal/model"
|
|
httpapi "github.com/pocket-id/pocket-id/backend/internal/utils/huma"
|
|
)
|
|
|
|
type TokenService interface {
|
|
GenerateAccessToken(user model.User, authenticationMethod string) (string, error)
|
|
VerifyAccessToken(tokenString string) (jwt.Token, error)
|
|
GetAuthenticationMethod(token jwt.Token) (string, error)
|
|
}
|
|
|
|
type AuditLogger interface {
|
|
Create(ctx context.Context, event model.AuditLogEvent, ipAddress, userAgent, userID string, data model.AuditLogData, tx *gorm.DB) (model.AuditLog, bool)
|
|
CreateNewSignInWithEmail(ctx context.Context, ipAddress, userAgent, userID string, tx *gorm.DB) model.AuditLog
|
|
}
|
|
|
|
type AppConfigProvider interface {
|
|
GetDbConfig() *model.AppConfig
|
|
}
|
|
|
|
type Dependencies struct {
|
|
DB *gorm.DB
|
|
AppURL string
|
|
|
|
Signer TokenService
|
|
AuditLog AuditLogger
|
|
AppConfig AppConfigProvider
|
|
}
|
|
|
|
type Module struct {
|
|
service *Service
|
|
handler *handler
|
|
}
|
|
|
|
func New(deps Dependencies) (*Module, error) {
|
|
service, err := newService(deps)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &Module{
|
|
service: service,
|
|
handler: newHandler(service, deps.AppConfig),
|
|
}, nil
|
|
}
|
|
|
|
// RegisterRoutes mounts the WebAuthn registration, login and reauthentication endpoints
|
|
func (m *Module) RegisterRoutes(api huma.API, userAuth func(*huma.Operation), loginRateLimit, reauthRateLimit func(huma.Context, func(huma.Context))) {
|
|
beginRegistration := webauthnOperation("begin-webauthn-registration", http.MethodGet, "/api/webauthn/register/start", "Begin WebAuthn registration")
|
|
userAuth(&beginRegistration)
|
|
httpapi.Register(api, beginRegistration, m.handler.beginRegistration)
|
|
|
|
verifyRegistration := webauthnOperation("finish-webauthn-registration", http.MethodPost, "/api/webauthn/register/finish", "Finish WebAuthn registration")
|
|
userAuth(&verifyRegistration)
|
|
httpapi.Register(api, verifyRegistration, m.handler.verifyRegistration)
|
|
|
|
httpapi.Register(api, webauthnOperation("begin-webauthn-login", http.MethodGet, "/api/webauthn/login/start", "Begin WebAuthn login"), m.handler.beginLogin)
|
|
|
|
verifyLogin := webauthnOperation("finish-webauthn-login", http.MethodPost, "/api/webauthn/login/finish", "Finish WebAuthn login")
|
|
verifyLogin.Middlewares = append(verifyLogin.Middlewares, loginRateLimit)
|
|
httpapi.Register(api, verifyLogin, m.handler.verifyLogin)
|
|
|
|
logout := webauthnOperation("webauthn-logout", http.MethodPost, "/api/webauthn/logout", "Log out")
|
|
logout.DefaultStatus = http.StatusNoContent
|
|
userAuth(&logout)
|
|
httpapi.Register(api, logout, m.handler.logout)
|
|
|
|
reauthenticate := webauthnOperation("webauthn-reauthenticate", http.MethodPost, "/api/webauthn/reauthenticate", "Reauthenticate")
|
|
reauthenticate.DefaultStatus = http.StatusNoContent
|
|
userAuth(&reauthenticate)
|
|
reauthenticate.Middlewares = append(reauthenticate.Middlewares, reauthRateLimit)
|
|
httpapi.Register(api, reauthenticate, m.handler.reauthenticate)
|
|
|
|
listCredentials := webauthnOperation("list-webauthn-credentials", http.MethodGet, "/api/webauthn/credentials", "List WebAuthn credentials")
|
|
userAuth(&listCredentials)
|
|
httpapi.Register(api, listCredentials, m.handler.listCredentials)
|
|
|
|
updateCredential := webauthnOperation("update-webauthn-credential", http.MethodPatch, "/api/webauthn/credentials/{id}", "Update WebAuthn credential")
|
|
userAuth(&updateCredential)
|
|
httpapi.Register(api, updateCredential, m.handler.updateCredential)
|
|
|
|
deleteCredential := webauthnOperation("delete-webauthn-credential", http.MethodDelete, "/api/webauthn/credentials/{id}", "Delete WebAuthn credential")
|
|
deleteCredential.DefaultStatus = http.StatusNoContent
|
|
userAuth(&deleteCredential)
|
|
httpapi.Register(api, deleteCredential, m.handler.deleteCredential)
|
|
}
|
|
|
|
func webauthnOperation(id, method, path, summary string) huma.Operation {
|
|
return huma.Operation{OperationID: id, Method: method, Path: path, Summary: summary, Tags: []string{"WebAuthn"}}
|
|
}
|
|
|
|
// ConsumeReauthenticationToken implements the OIDC module's ReauthenticationTokenConsumer interface
|
|
func (m *Module) ConsumeReauthenticationToken(ctx context.Context, tx *gorm.DB, token string, userID string) (time.Time, error) {
|
|
return m.service.ConsumeReauthenticationToken(ctx, tx, token, userID)
|
|
}
|
|
|
|
// ListCredentials returns the passkeys registered for the given user
|
|
// It is consumed by the user controller for the admin "manage passkeys" view
|
|
func (m *Module) ListCredentials(ctx context.Context, userID string) ([]model.WebauthnCredential, error) {
|
|
return m.service.ListCredentials(ctx, userID)
|
|
}
|
|
|
|
// DeleteCredential removes a passkey, optionally on behalf of an admin acting for another user
|
|
// It is consumed by the user controller for the admin "manage passkeys" view
|
|
func (m *Module) DeleteCredential(ctx context.Context, userID, credentialID, ipAddress, userAgent, actorUserID string) error {
|
|
return m.service.DeleteCredential(ctx, userID, credentialID, ipAddress, userAgent, actorUserID)
|
|
}
|