feat: support time_zone for user registration context

This commit is contained in:
Ilia Denisov
2026-04-09 09:00:06 +02:00
parent e6b73a8f55
commit 7043af4cb3
40 changed files with 3452 additions and 164 deletions
@@ -11,10 +11,13 @@ import (
"galaxy/authsession/internal/domain/common"
"galaxy/authsession/internal/domain/devicesession"
"galaxy/authsession/internal/domain/userresolution"
"galaxy/authsession/internal/ports"
"galaxy/authsession/internal/service/shared"
"galaxy/authsession/internal/testkit"
)
const confirmEmailCodeTimeZone = "Europe/Kaliningrad"
func TestExecuteConfirmsChallengeForExistingUser(t *testing.T) {
t.Parallel()
@@ -31,6 +34,7 @@ func TestExecuteConfirmsChallengeForExistingUser(t *testing.T) {
ChallengeID: "challenge-1",
Code: "654321",
ClientPublicKey: publicKeyString(),
TimeZone: confirmEmailCodeTimeZone,
})
if err != nil {
require.Failf(t, "test failed", "Execute() returned error: %v", err)
@@ -75,6 +79,7 @@ func TestExecuteConfirmsChallengeByCreatingUser(t *testing.T) {
ChallengeID: "challenge-1",
Code: "654321",
ClientPublicKey: publicKeyString(),
TimeZone: confirmEmailCodeTimeZone,
})
if err != nil {
require.Failf(t, "test failed", "Execute() returned error: %v", err)
@@ -114,6 +119,7 @@ func TestExecuteConfirmsSuppressedChallenge(t *testing.T) {
ChallengeID: "challenge-1",
Code: "654321",
ClientPublicKey: publicKeyString(),
TimeZone: confirmEmailCodeTimeZone,
})
if err != nil {
require.Failf(t, "test failed", "Execute() returned error: %v", err)
@@ -132,6 +138,7 @@ func TestExecuteReturnsChallengeNotFound(t *testing.T) {
ChallengeID: "missing",
Code: "654321",
ClientPublicKey: publicKeyString(),
TimeZone: confirmEmailCodeTimeZone,
})
if shared.CodeOf(err) != shared.ErrorCodeChallengeNotFound {
require.Failf(t, "test failed", "Execute() error code = %q, want %q", shared.CodeOf(err), shared.ErrorCodeChallengeNotFound)
@@ -152,6 +159,7 @@ func TestExecuteReturnsChallengeExpiredAndMarksExpired(t *testing.T) {
ChallengeID: "challenge-1",
Code: "654321",
ClientPublicKey: publicKeyString(),
TimeZone: confirmEmailCodeTimeZone,
})
if shared.CodeOf(err) != shared.ErrorCodeChallengeExpired {
require.Failf(t, "test failed", "Execute() error code = %q, want %q", shared.CodeOf(err), shared.ErrorCodeChallengeExpired)
@@ -194,6 +202,7 @@ func TestExecuteReturnsChallengeExpiredForConfirmedChallengeAfterRetentionWindow
ChallengeID: "challenge-1",
Code: "654321",
ClientPublicKey: publicKeyString(),
TimeZone: confirmEmailCodeTimeZone,
})
if shared.CodeOf(err) != shared.ErrorCodeChallengeExpired {
require.Failf(t, "test failed", "Execute() error code = %q, want %q", shared.CodeOf(err), shared.ErrorCodeChallengeExpired)
@@ -220,12 +229,32 @@ func TestExecuteReturnsInvalidClientPublicKey(t *testing.T) {
ChallengeID: "challenge-1",
Code: "654321",
ClientPublicKey: "invalid",
TimeZone: confirmEmailCodeTimeZone,
})
if shared.CodeOf(err) != shared.ErrorCodeInvalidClientPublicKey {
require.Failf(t, "test failed", "Execute() error code = %q, want %q", shared.CodeOf(err), shared.ErrorCodeInvalidClientPublicKey)
}
}
func TestExecuteReturnsInvalidRequestForInvalidTimeZone(t *testing.T) {
t.Parallel()
service := mustNewConfirmService(t, newConfirmDeps(t))
_, err := service.Execute(context.Background(), Input{
ChallengeID: "challenge-1",
Code: "654321",
ClientPublicKey: publicKeyString(),
TimeZone: "Mars/Olympus",
})
if shared.CodeOf(err) != shared.ErrorCodeInvalidRequest {
require.Failf(t, "test failed", "Execute() error code = %q, want %q", shared.CodeOf(err), shared.ErrorCodeInvalidRequest)
}
if err == nil || err.Error() != "time_zone must be a valid IANA time zone name" {
require.Failf(t, "test failed", "Execute() error = %v, want invalid time_zone detail", err)
}
}
func TestExecuteInvalidCodeIncrementsAttempts(t *testing.T) {
t.Parallel()
@@ -239,6 +268,7 @@ func TestExecuteInvalidCodeIncrementsAttempts(t *testing.T) {
ChallengeID: "challenge-1",
Code: "000000",
ClientPublicKey: publicKeyString(),
TimeZone: confirmEmailCodeTimeZone,
})
if shared.CodeOf(err) != shared.ErrorCodeInvalidCode {
require.Failf(t, "test failed", "Execute() error code = %q, want %q", shared.CodeOf(err), shared.ErrorCodeInvalidCode)
@@ -268,6 +298,7 @@ func TestExecuteFifthInvalidAttemptMarksChallengeFailed(t *testing.T) {
ChallengeID: "challenge-1",
Code: "000000",
ClientPublicKey: publicKeyString(),
TimeZone: confirmEmailCodeTimeZone,
})
if shared.CodeOf(err) != shared.ErrorCodeInvalidCode {
require.Failf(t, "test failed", "Execute() error code = %q, want %q", shared.CodeOf(err), shared.ErrorCodeInvalidCode)
@@ -304,6 +335,7 @@ func TestExecuteDoesNotCreateSessionAfterTooManyAttempts(t *testing.T) {
ChallengeID: "challenge-1",
Code: "654321",
ClientPublicKey: publicKeyString(),
TimeZone: confirmEmailCodeTimeZone,
})
if shared.CodeOf(err) != shared.ErrorCodeInvalidCode {
require.Failf(t, "test failed", "Execute() error code = %q, want %q", shared.CodeOf(err), shared.ErrorCodeInvalidCode)
@@ -337,6 +369,7 @@ func TestExecuteReturnsSameSessionIDForIdempotentRetryAndRepublishes(t *testing.
ChallengeID: "challenge-1",
Code: "654321",
ClientPublicKey: publicKeyString(),
TimeZone: confirmEmailCodeTimeZone,
})
if err != nil {
require.Failf(t, "test failed", "Execute() returned error: %v", err)
@@ -370,6 +403,7 @@ func TestExecuteReturnsInvalidCodeForDifferentKeyDuringIdempotentRetry(t *testin
ChallengeID: "challenge-1",
Code: "654321",
ClientPublicKey: alternatePublicKeyString(),
TimeZone: confirmEmailCodeTimeZone,
})
if shared.CodeOf(err) != shared.ErrorCodeInvalidCode {
require.Failf(t, "test failed", "Execute() error code = %q, want %q", shared.CodeOf(err), shared.ErrorCodeInvalidCode)
@@ -425,6 +459,7 @@ func TestExecuteReturnsInvalidCodeForNonConfirmableStates(t *testing.T) {
ChallengeID: "challenge-1",
Code: "654321",
ClientPublicKey: publicKeyString(),
TimeZone: confirmEmailCodeTimeZone,
})
if shared.CodeOf(err) != shared.ErrorCodeInvalidCode {
require.Failf(t, "test failed", "Execute() error code = %q, want %q", shared.CodeOf(err), shared.ErrorCodeInvalidCode)
@@ -457,6 +492,7 @@ func TestExecuteMarksChallengeFailedAndReturnsBlockedByPolicy(t *testing.T) {
ChallengeID: "challenge-1",
Code: "654321",
ClientPublicKey: publicKeyString(),
TimeZone: confirmEmailCodeTimeZone,
})
if shared.CodeOf(err) != shared.ErrorCodeBlockedByPolicy {
require.Failf(t, "test failed", "Execute() error code = %q, want %q", shared.CodeOf(err), shared.ErrorCodeBlockedByPolicy)
@@ -492,6 +528,7 @@ func TestExecuteReturnsSessionLimitExceededWithoutConsumingChallenge(t *testing.
ChallengeID: "challenge-1",
Code: "654321",
ClientPublicKey: publicKeyString(),
TimeZone: confirmEmailCodeTimeZone,
})
if shared.CodeOf(err) != shared.ErrorCodeSessionLimitExceeded {
require.Failf(t, "test failed", "Execute() error code = %q, want %q", shared.CodeOf(err), shared.ErrorCodeSessionLimitExceeded)
@@ -509,6 +546,57 @@ func TestExecuteReturnsSessionLimitExceededWithoutConsumingChallenge(t *testing.
}
}
func TestExecutePassesRegistrationContextToUserDirectory(t *testing.T) {
t.Parallel()
deps := newConfirmDeps(t)
recordingDirectory := &recordingEnsureUserDirectory{delegate: deps.userDirectory}
deps.userDirectory = nil
if err := recordingDirectory.delegate.QueueCreatedUserIDs(common.UserID("user-created")); err != nil {
require.Failf(t, "test failed", "QueueCreatedUserIDs() returned error: %v", err)
}
if err := deps.challengeStore.Create(context.Background(), sentChallengeFixture(t, deps.hasher, "challenge-1", "new@example.com", "654321", deps.now.Add(-time.Minute), deps.now.Add(time.Minute))); err != nil {
require.Failf(t, "test failed", "Create() returned error: %v", err)
}
service, err := New(
deps.challengeStore,
deps.sessionStore,
recordingDirectory,
deps.configProvider,
deps.publisher,
deps.idGenerator,
deps.hasher,
testkit.FixedClock{Time: deps.now},
)
if err != nil {
require.Failf(t, "test failed", "New() returned error: %v", err)
}
_, err = service.Execute(context.Background(), Input{
ChallengeID: "challenge-1",
Code: "654321",
ClientPublicKey: publicKeyString(),
TimeZone: confirmEmailCodeTimeZone,
})
if err != nil {
require.Failf(t, "test failed", "Execute() returned error: %v", err)
}
if recordingDirectory.lastEnsureInput.Email != common.Email("new@example.com") {
require.Failf(t, "test failed", "last ensure email = %q, want %q", recordingDirectory.lastEnsureInput.Email, common.Email("new@example.com"))
}
if recordingDirectory.lastEnsureInput.RegistrationContext == nil {
require.FailNow(t, "last ensure registration context = nil, want value")
}
if recordingDirectory.lastEnsureInput.RegistrationContext.PreferredLanguage != "en" {
require.Failf(t, "test failed", "preferred language = %q, want %q", recordingDirectory.lastEnsureInput.RegistrationContext.PreferredLanguage, "en")
}
if recordingDirectory.lastEnsureInput.RegistrationContext.TimeZone != confirmEmailCodeTimeZone {
require.Failf(t, "test failed", "time zone = %q, want %q", recordingDirectory.lastEnsureInput.RegistrationContext.TimeZone, confirmEmailCodeTimeZone)
}
}
func TestExecuteReturnsServiceUnavailableThenSucceedsIdempotentlyAfterPublishFailure(t *testing.T) {
t.Parallel()
@@ -526,6 +614,7 @@ func TestExecuteReturnsServiceUnavailableThenSucceedsIdempotentlyAfterPublishFai
ChallengeID: "challenge-1",
Code: "654321",
ClientPublicKey: publicKeyString(),
TimeZone: confirmEmailCodeTimeZone,
})
if shared.CodeOf(err) != shared.ErrorCodeServiceUnavailable {
require.Failf(t, "test failed", "first Execute() error code = %q, want %q", shared.CodeOf(err), shared.ErrorCodeServiceUnavailable)
@@ -536,6 +625,7 @@ func TestExecuteReturnsServiceUnavailableThenSucceedsIdempotentlyAfterPublishFai
ChallengeID: "challenge-1",
Code: "654321",
ClientPublicKey: publicKeyString(),
TimeZone: confirmEmailCodeTimeZone,
})
if err != nil {
require.Failf(t, "test failed", "second Execute() returned error: %v", err)
@@ -680,3 +770,33 @@ func publicKeyString() string {
func alternatePublicKeyString() string {
return "AQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQE="
}
// recordingEnsureUserDirectory records the last ensure input while delegating
// behavior to the in-memory testkit directory.
type recordingEnsureUserDirectory struct {
delegate *testkit.InMemoryUserDirectory
lastEnsureInput ports.EnsureUserInput
}
func (d *recordingEnsureUserDirectory) ResolveByEmail(ctx context.Context, email common.Email) (userresolution.Result, error) {
return d.delegate.ResolveByEmail(ctx, email)
}
func (d *recordingEnsureUserDirectory) ExistsByUserID(ctx context.Context, userID common.UserID) (bool, error) {
return d.delegate.ExistsByUserID(ctx, userID)
}
func (d *recordingEnsureUserDirectory) EnsureUserByEmail(ctx context.Context, input ports.EnsureUserInput) (ports.EnsureUserResult, error) {
d.lastEnsureInput = input
return d.delegate.EnsureUserByEmail(ctx, input)
}
func (d *recordingEnsureUserDirectory) BlockByUserID(ctx context.Context, input ports.BlockUserByIDInput) (ports.BlockUserResult, error) {
return d.delegate.BlockByUserID(ctx, input)
}
func (d *recordingEnsureUserDirectory) BlockByEmail(ctx context.Context, input ports.BlockUserByEmailInput) (ports.BlockUserResult, error) {
return d.delegate.BlockByEmail(ctx, input)
}
var _ ports.UserDirectory = (*recordingEnsureUserDirectory)(nil)