feat: add ability to force email verification (#1837)

This commit is contained in:
Elias Schneider
2026-10-13 22:04:33 +02:00
committed by GitHub
parent eb8ea6bed2
commit 0ffeb170cf
81 changed files with 2310 additions and 263 deletions
@@ -10,6 +10,7 @@ import (
"github.com/stretchr/testify/require"
"gorm.io/gorm"
"github.com/pocket-id/pocket-id/backend/internal/appconfig"
"github.com/pocket-id/pocket-id/backend/internal/apperror"
"github.com/pocket-id/pocket-id/backend/internal/auditlogs"
"github.com/pocket-id/pocket-id/backend/internal/model"
@@ -31,7 +32,7 @@ func (f *fakeAuditLogger) Create(_ context.Context, event auditlogs.Event, _, _,
func TestAuthorizationServiceAuthorizeLogsClientAuthorization(t *testing.T) {
db := testutils.NewDatabaseForTest(t)
auditLogger := &fakeAuditLogger{}
service := newAuthorizationService(db, newInteractionSessionService(db), newClaimsService(db, nil, "", nil), nil, auditLogger, nil)
service := newAuthorizationService(db, newInteractionSessionService(db), newClaimsService(db, nil, "", nil, appconfig.StaticResolver{}), nil, auditLogger, nil)
const (
userID = "test-user"
@@ -66,7 +67,7 @@ func TestAuthorizationServiceAuthorizeLogsClientAuthorization(t *testing.T) {
func TestAuthorizationServiceRejectsCustomScopeWithoutResource(t *testing.T) {
db := testutils.NewDatabaseForTest(t)
service := newAuthorizationService(db, newInteractionSessionService(db), newClaimsService(db, nil, "", nil), nil, nil, nil)
service := newAuthorizationService(db, newInteractionSessionService(db), newClaimsService(db, nil, "", nil, appconfig.StaticResolver{}), nil, nil, nil)
const (
userID = "test-user"
@@ -106,7 +107,7 @@ func TestAuthorizationServiceCollapsesResourceErrorsBeforeAuthentication(t *test
t.Helper()
db := testutils.NewDatabaseForTest(t)
require.NoError(t, db.Create(&model.OidcClient{Base: model.Base{ID: clientID}, Name: "Test Client"}).Error)
service := newAuthorizationService(db, newInteractionSessionService(db), newClaimsService(db, nil, "", nil), nil, nil, apiAccess)
service := newAuthorizationService(db, newInteractionSessionService(db), newClaimsService(db, nil, "", nil, appconfig.StaticResolver{}), nil, nil, apiAccess)
requester := newTestAuthorizeRequesterWithForm("resource-probe", clientID, url.Values{"resource": {resource}})
_, err := service.authorize(t.Context(), authorizeInput{
@@ -140,7 +141,7 @@ func TestAuthorizationServiceCollapsesResourceErrorsBeforeAuthentication(t *test
func TestAuthorizationServiceConsentStepLogsNewClientAuthorization(t *testing.T) {
db := testutils.NewDatabaseForTest(t)
auditLogger := &fakeAuditLogger{}
service := newAuthorizationService(db, newInteractionSessionService(db), newClaimsService(db, nil, "", nil), nil, auditLogger, nil)
service := newAuthorizationService(db, newInteractionSessionService(db), newClaimsService(db, nil, "", nil, appconfig.StaticResolver{}), nil, auditLogger, nil)
const (
userID = "test-user"
@@ -183,7 +184,7 @@ func TestAuthorizationServiceConsentMergesAudienceQualifiedScopeKeys(t *testing.
apiB = "https://api-b.example.com"
)
service := newAuthorizationService(db, newInteractionSessionService(db), newClaimsService(db, nil, "", nil), nil, auditLogger, userAccess(map[string][]string{
service := newAuthorizationService(db, newInteractionSessionService(db), newClaimsService(db, nil, "", nil, appconfig.StaticResolver{}), nil, auditLogger, userAccess(map[string][]string{
apiA: {"read"},
apiB: {"read"},
}))
@@ -250,7 +251,7 @@ func TestAuthorizationServiceRequiresConsentForScopelessAPIAccess(t *testing.T)
)
// The client may reach the API but was granted none of its permissions, which is the MCP-style scopeless grant
service := newAuthorizationService(db, newInteractionSessionService(db), newClaimsService(db, nil, "", nil), nil, nil, userAccess(map[string][]string{
service := newAuthorizationService(db, newInteractionSessionService(db), newClaimsService(db, nil, "", nil, appconfig.StaticResolver{}), nil, nil, userAccess(map[string][]string{
api: {},
}))
@@ -292,7 +293,7 @@ func TestAuthorizationServiceRequiresConsentForScopelessAPIAccess(t *testing.T)
func TestAuthorizationServiceAuthorizeConsumesInteractionSession(t *testing.T) {
db := testutils.NewDatabaseForTest(t)
service := newAuthorizationService(db, newInteractionSessionService(db), newClaimsService(db, nil, "", nil), nil, nil, nil)
service := newAuthorizationService(db, newInteractionSessionService(db), newClaimsService(db, nil, "", nil, appconfig.StaticResolver{}), nil, nil, nil)
const (
userID = "test-user"
@@ -376,7 +377,7 @@ func TestInteractionSessionServiceGetRejectsExpiredSession(t *testing.T) {
func TestInteractionAPIClassifiesMissingSession(t *testing.T) {
db := testutils.NewDatabaseForTest(t)
service := newAuthorizationService(db, newInteractionSessionService(db), newClaimsService(db, nil, "", nil), nil, nil, nil)
service := newAuthorizationService(db, newInteractionSessionService(db), newClaimsService(db, nil, "", nil, appconfig.StaticResolver{}), nil, nil, nil)
_, err := service.getInteractionSession(t.Context(), "missing-interaction")
require.True(t, apperror.IsCode(err, apperror.CodeNotFound))
@@ -387,7 +388,7 @@ func TestInteractionAPIClassifiesMissingSession(t *testing.T) {
func TestAuthorizationServiceAuthorizeBindsScopesToInteractionSession(t *testing.T) {
db := testutils.NewDatabaseForTest(t)
service := newAuthorizationService(db, newInteractionSessionService(db), newClaimsService(db, nil, "", nil), nil, nil, nil)
service := newAuthorizationService(db, newInteractionSessionService(db), newClaimsService(db, nil, "", nil, appconfig.StaticResolver{}), nil, nil, nil)
const (
userID = "test-user"
@@ -442,7 +443,7 @@ func TestAuthorizationServiceAuthorizeBindsScopesToInteractionSession(t *testing
func TestAuthorizationServiceAuthorizeRejectsInteractionSessionOfOtherClient(t *testing.T) {
db := testutils.NewDatabaseForTest(t)
service := newAuthorizationService(db, newInteractionSessionService(db), newClaimsService(db, nil, "", nil), nil, nil, nil)
service := newAuthorizationService(db, newInteractionSessionService(db), newClaimsService(db, nil, "", nil, appconfig.StaticResolver{}), nil, nil, nil)
const (
userID = "test-user"
@@ -481,7 +482,7 @@ func TestAuthorizationServiceAuthorizeRejectsInteractionSessionOfOtherClient(t *
func TestAuthorizationServiceAuthorizeSwitchesUserAndResetsRequirements(t *testing.T) {
db := testutils.NewDatabaseForTest(t)
service := newAuthorizationService(db, newInteractionSessionService(db), newClaimsService(db, nil, "", nil), nil, nil, nil)
service := newAuthorizationService(db, newInteractionSessionService(db), newClaimsService(db, nil, "", nil, appconfig.StaticResolver{}), nil, nil, nil)
const (
userID = "test-user"
@@ -541,7 +542,7 @@ func TestAuthorizationServiceAuthorizeSwitchesUserAndResetsRequirements(t *testi
func TestAuthorizationServiceAuthorizeRequiresLoginForUserBoundInteraction(t *testing.T) {
db := testutils.NewDatabaseForTest(t)
service := newAuthorizationService(db, newInteractionSessionService(db), newClaimsService(db, nil, "", nil), nil, nil, nil)
service := newAuthorizationService(db, newInteractionSessionService(db), newClaimsService(db, nil, "", nil, appconfig.StaticResolver{}), nil, nil, nil)
const (
userID = "test-user"
@@ -574,7 +575,7 @@ func TestAuthorizationServiceAuthorizeRequiresLoginForUserBoundInteraction(t *te
func TestAuthorizationServiceCompleteInteractionBindsUserToSession(t *testing.T) {
db := testutils.NewDatabaseForTest(t)
service := newAuthorizationService(db, newInteractionSessionService(db), newClaimsService(db, nil, "", nil), nil, nil, nil)
service := newAuthorizationService(db, newInteractionSessionService(db), newClaimsService(db, nil, "", nil, appconfig.StaticResolver{}), nil, nil, nil)
const (
userID = "test-user"
@@ -610,7 +611,7 @@ func TestAuthorizationServiceCompleteInteractionBindsUserToSession(t *testing.T)
func TestAuthorizationServiceCompleteInteractionSwitchesUserAndResetsRequirements(t *testing.T) {
db := testutils.NewDatabaseForTest(t)
service := newAuthorizationService(db, newInteractionSessionService(db), newClaimsService(db, nil, "", nil), nil, nil, nil)
service := newAuthorizationService(db, newInteractionSessionService(db), newClaimsService(db, nil, "", nil, appconfig.StaticResolver{}), nil, nil, nil)
const (
userID = "test-user"
@@ -663,7 +664,7 @@ func TestAuthorizationServiceCompleteInteractionSwitchesUserAndResetsRequirement
// still be required for that user rather than being inherited from the initiator.
func TestAuthorizationServiceSelectAccountRecomputesConsentForSelectedUser(t *testing.T) {
db := testutils.NewDatabaseForTest(t)
service := newAuthorizationService(db, newInteractionSessionService(db), newClaimsService(db, nil, "", nil), nil, nil, nil)
service := newAuthorizationService(db, newInteractionSessionService(db), newClaimsService(db, nil, "", nil, appconfig.StaticResolver{}), nil, nil, nil)
const (
initiatorID = "initiator-user"
@@ -725,7 +726,7 @@ func TestValidateClientPKCERequirement(t *testing.T) {
// flag is honored for confidential clients (fosite only enforces PKCE for public clients).
func TestAuthorizationServiceAuthorizeEnforcesPerClientPKCE(t *testing.T) {
db := testutils.NewDatabaseForTest(t)
service := newAuthorizationService(db, newInteractionSessionService(db), newClaimsService(db, nil, "", nil), nil, nil, nil)
service := newAuthorizationService(db, newInteractionSessionService(db), newClaimsService(db, nil, "", nil, appconfig.StaticResolver{}), nil, nil, nil)
const (
userID = "test-user"
@@ -761,7 +762,7 @@ func TestAuthorizationServiceAuthorizeEnforcesPerClientPKCE(t *testing.T) {
func TestAuthorizationServiceAuthorizePARRequiredClient(t *testing.T) {
db := testutils.NewDatabaseForTest(t)
service := newAuthorizationService(db, newInteractionSessionService(db), newClaimsService(db, nil, "", nil), nil, nil, nil)
service := newAuthorizationService(db, newInteractionSessionService(db), newClaimsService(db, nil, "", nil, appconfig.StaticResolver{}), nil, nil, nil)
const (
userID = "test-user"
@@ -826,7 +827,7 @@ func TestAuthorizationServiceAuthorizePARRequiredClient(t *testing.T) {
func TestAuthorizationServiceInteractionRequestQuery(t *testing.T) {
db := testutils.NewDatabaseForTest(t)
service := newAuthorizationService(db, newInteractionSessionService(db), newClaimsService(db, nil, "", nil), nil, nil, nil)
service := newAuthorizationService(db, newInteractionSessionService(db), newClaimsService(db, nil, "", nil, appconfig.StaticResolver{}), nil, nil, nil)
const (
clientID = "test-client"
@@ -866,7 +867,7 @@ func TestAuthorizationServiceInteractionRequestQuery(t *testing.T) {
func TestAuthorizationServiceAuthorizeUsesLoginAuthenticationTime(t *testing.T) {
db := testutils.NewDatabaseForTest(t)
service := newAuthorizationService(db, newInteractionSessionService(db), newClaimsService(db, nil, "", nil), nil, nil, nil)
service := newAuthorizationService(db, newInteractionSessionService(db), newClaimsService(db, nil, "", nil, appconfig.StaticResolver{}), nil, nil, nil)
const (
userID = "test-user"
@@ -908,7 +909,7 @@ func TestAuthorizationServiceAuthorizeUsesLoginAuthenticationTime(t *testing.T)
func TestAuthorizationServiceAuthorizeRequiresReauthenticationWhenMaxAgeExceeded(t *testing.T) {
db := testutils.NewDatabaseForTest(t)
service := newAuthorizationService(db, newInteractionSessionService(db), newClaimsService(db, nil, "", nil), nil, nil, nil)
service := newAuthorizationService(db, newInteractionSessionService(db), newClaimsService(db, nil, "", nil, appconfig.StaticResolver{}), nil, nil, nil)
const (
userID = "test-user"
@@ -945,7 +946,7 @@ func TestAuthorizationServiceAuthorizeRequiresReauthenticationWhenMaxAgeExceeded
func TestAuthorizationServiceAuthorizeUsesCompletedReauthenticationTime(t *testing.T) {
db := testutils.NewDatabaseForTest(t)
service := newAuthorizationService(db, newInteractionSessionService(db), newClaimsService(db, nil, "", nil), nil, nil, nil)
service := newAuthorizationService(db, newInteractionSessionService(db), newClaimsService(db, nil, "", nil, appconfig.StaticResolver{}), nil, nil, nil)
const (
userID = "test-user"
@@ -992,7 +993,7 @@ func TestAuthorizationServiceAuthorizeUsesCompletedReauthenticationTime(t *testi
func TestAuthorizationServiceAuthorizeUsesOriginalInteractionRequestTime(t *testing.T) {
db := testutils.NewDatabaseForTest(t)
service := newAuthorizationService(db, newInteractionSessionService(db), newClaimsService(db, nil, "", nil), nil, nil, nil)
service := newAuthorizationService(db, newInteractionSessionService(db), newClaimsService(db, nil, "", nil, appconfig.StaticResolver{}), nil, nil, nil)
const (
userID = "test-user"
@@ -1143,7 +1144,7 @@ func TestConsentRequired(t *testing.T) {
func TestAuthorizationServiceSkipConsentGrantsWithoutInteraction(t *testing.T) {
db := testutils.NewDatabaseForTest(t)
auditLogger := &fakeAuditLogger{}
service := newAuthorizationService(db, newInteractionSessionService(db), newClaimsService(db, nil, "", nil), nil, auditLogger, nil)
service := newAuthorizationService(db, newInteractionSessionService(db), newClaimsService(db, nil, "", nil, appconfig.StaticResolver{}), nil, auditLogger, nil)
const (
userID = "test-user"
@@ -1176,7 +1177,7 @@ func TestAuthorizationServiceSkipConsentGrantsWithoutInteraction(t *testing.T) {
// A client with SkipConsent must still show the consent screen when the request explicitly asks for it with prompt=consent
func TestAuthorizationServiceSkipConsentHonorsPromptConsent(t *testing.T) {
db := testutils.NewDatabaseForTest(t)
service := newAuthorizationService(db, newInteractionSessionService(db), newClaimsService(db, nil, "", nil), nil, nil, nil)
service := newAuthorizationService(db, newInteractionSessionService(db), newClaimsService(db, nil, "", nil, appconfig.StaticResolver{}), nil, nil, nil)
const (
userID = "test-user"