Files

133 lines
3.9 KiB
Go

package onetimeaccess
import (
"context"
"fmt"
"net/http"
"time"
"github.com/pocket-id/pocket-id/backend/internal/common"
"github.com/pocket-id/pocket-id/backend/internal/dto"
"github.com/pocket-id/pocket-id/backend/internal/utils/cookie"
httpapi "github.com/pocket-id/pocket-id/backend/internal/utils/huma"
)
const defaultTokenDuration = 15 * time.Minute
type tokenCreateInput struct {
ID string `path:"id"`
Body tokenCreateDto
}
type ownTokenCreateInput struct {
Body tokenCreateDto
}
type emailAsUnauthenticatedUserInput struct {
Body emailAsUnauthenticatedUserDto
}
type emailAsAdminInput struct {
ID string `path:"id"`
Body emailAsAdminDto
}
type tokenExchangeInput struct {
Token string `path:"token"`
}
type cookieOutput struct {
SetCookie []http.Cookie `header:"Set-Cookie"`
}
type userOutput struct {
SetCookie []http.Cookie `header:"Set-Cookie"`
Body dto.UserDto
}
type handler struct {
service *Service
appConfig AppConfigResolver
}
func newHandler(service *Service, appConfig AppConfigResolver) *handler {
return &handler{service: service, appConfig: appConfig}
}
func (h *handler) createOwnToken(ctx context.Context, _ *ownTokenCreateInput) (*httpapi.BodyOutput[map[string]string], error) {
return h.createToken(ctx, httpapi.UserID(ctx), defaultTokenDuration)
}
func (h *handler) createTokenForUser(ctx context.Context, input *tokenCreateInput) (*httpapi.BodyOutput[map[string]string], error) {
ttl := input.Body.TTL.Duration
if ttl <= 0 {
ttl = defaultTokenDuration
}
return h.createToken(ctx, input.ID, ttl)
}
func (h *handler) createToken(ctx context.Context, userID string, ttl time.Duration) (*httpapi.BodyOutput[map[string]string], error) {
if userID == "" {
return nil, &common.UserIdNotProvidedError{}
}
token, err := h.service.CreateToken(ctx, userID, ttl)
if err != nil {
return nil, err
}
return &httpapi.BodyOutput[map[string]string]{Body: map[string]string{"token": token}}, nil
}
func (h *handler) requestEmailAsUnauthenticatedUser(ctx context.Context, input *emailAsUnauthenticatedUserInput) (*cookieOutput, error) {
dbConfig, err := h.appConfig.GetConfig(ctx)
if err != nil {
return nil, fmt.Errorf("error loading app configuration: %w", err)
}
deviceToken, err := h.service.RequestOneTimeAccessEmailAsUnauthenticatedUser(ctx, dbConfig, input.Body.Email, input.Body.RedirectPath)
if err != nil {
return nil, err
}
return &cookieOutput{SetCookie: []http.Cookie{*cookie.NewDeviceTokenCookie(deviceToken)}}, nil
}
func (h *handler) requestEmailAsAdmin(ctx context.Context, input *emailAsAdminInput) (*httpapi.EmptyOutput, error) {
dbConfig, err := h.appConfig.GetConfig(ctx)
if err != nil {
return nil, fmt.Errorf("error loading app configuration: %w", err)
}
ttl := input.Body.TTL.Duration
if ttl <= 0 {
ttl = defaultTokenDuration
}
if err := h.service.RequestOneTimeAccessEmailAsAdmin(ctx, dbConfig, input.ID, ttl); err != nil {
return nil, err
}
return &httpapi.EmptyOutput{}, nil
}
func (h *handler) exchangeToken(ctx context.Context, input *tokenExchangeInput) (*userOutput, error) {
dbConfig, err := h.appConfig.GetConfig(ctx)
if err != nil {
return nil, fmt.Errorf("error loading app configuration: %w", err)
}
if len(input.Token) != 6 && len(input.Token) != 16 {
return nil, &common.TokenInvalidOrExpiredError{}
}
deviceToken := ""
if requestCookie, err := httpapi.Cookie(ctx, cookie.DeviceTokenCookieName); err == nil {
deviceToken = requestCookie.Value
}
user, token, err := h.service.ExchangeToken(ctx, dbConfig, input.Token, deviceToken, httpapi.ClientIP(ctx), httpapi.UserAgent(ctx))
if err != nil {
return nil, err
}
var output dto.UserDto
if err := dto.MapStruct(user, &output); err != nil {
return nil, err
}
maxAge := int(dbConfig.SessionDuration.AsDurationMinutes().Seconds())
return &userOutput{SetCookie: []http.Cookie{*cookie.NewAccessTokenCookie(maxAge, token)}, Body: output}, nil
}