feat(account): restore the confirm deeplink token (domain layer)
Re-apply the deeplink backend deferred out of PR1a: migration 00006 adds email_confirmations.purpose + link_token_hash (hand-edited jet), each issued code now carries an opaque 256-bit token (only its SHA-256 stored), and ConfirmByToken resolves a token to a login (confirm + clear guest) or a link (attach when free, signal merge when owned elsewhere). issueCode now embeds the /app/#/confirm/<token> link in the email. The confirm endpoint, gateway RPC and SPA route follow.
This commit is contained in:
@@ -21,4 +21,6 @@ type EmailConfirmations struct {
|
||||
Attempts int16
|
||||
ConsumedAt *time.Time
|
||||
CreatedAt time.Time
|
||||
Purpose string
|
||||
LinkTokenHash *string
|
||||
}
|
||||
|
||||
@@ -25,6 +25,8 @@ type emailConfirmationsTable struct {
|
||||
Attempts postgres.ColumnInteger
|
||||
ConsumedAt postgres.ColumnTimestampz
|
||||
CreatedAt postgres.ColumnTimestampz
|
||||
Purpose postgres.ColumnString
|
||||
LinkTokenHash postgres.ColumnString
|
||||
|
||||
AllColumns postgres.ColumnList
|
||||
MutableColumns postgres.ColumnList
|
||||
@@ -74,9 +76,11 @@ func newEmailConfirmationsTableImpl(schemaName, tableName, alias string) emailCo
|
||||
AttemptsColumn = postgres.IntegerColumn("attempts")
|
||||
ConsumedAtColumn = postgres.TimestampzColumn("consumed_at")
|
||||
CreatedAtColumn = postgres.TimestampzColumn("created_at")
|
||||
allColumns = postgres.ColumnList{ConfirmationIDColumn, AccountIDColumn, EmailColumn, CodeHashColumn, ExpiresAtColumn, AttemptsColumn, ConsumedAtColumn, CreatedAtColumn}
|
||||
mutableColumns = postgres.ColumnList{AccountIDColumn, EmailColumn, CodeHashColumn, ExpiresAtColumn, AttemptsColumn, ConsumedAtColumn, CreatedAtColumn}
|
||||
defaultColumns = postgres.ColumnList{AttemptsColumn, CreatedAtColumn}
|
||||
PurposeColumn = postgres.StringColumn("purpose")
|
||||
LinkTokenHashColumn = postgres.StringColumn("link_token_hash")
|
||||
allColumns = postgres.ColumnList{ConfirmationIDColumn, AccountIDColumn, EmailColumn, CodeHashColumn, ExpiresAtColumn, AttemptsColumn, ConsumedAtColumn, CreatedAtColumn, PurposeColumn, LinkTokenHashColumn}
|
||||
mutableColumns = postgres.ColumnList{AccountIDColumn, EmailColumn, CodeHashColumn, ExpiresAtColumn, AttemptsColumn, ConsumedAtColumn, CreatedAtColumn, PurposeColumn, LinkTokenHashColumn}
|
||||
defaultColumns = postgres.ColumnList{AttemptsColumn, CreatedAtColumn, PurposeColumn}
|
||||
)
|
||||
|
||||
return emailConfirmationsTable{
|
||||
@@ -91,6 +95,8 @@ func newEmailConfirmationsTableImpl(schemaName, tableName, alias string) emailCo
|
||||
Attempts: AttemptsColumn,
|
||||
ConsumedAt: ConsumedAtColumn,
|
||||
CreatedAt: CreatedAtColumn,
|
||||
Purpose: PurposeColumn,
|
||||
LinkTokenHash: LinkTokenHashColumn,
|
||||
|
||||
AllColumns: allColumns,
|
||||
MutableColumns: mutableColumns,
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
-- Add the confirm deeplink token and the confirmation purpose to email_confirmations.
|
||||
-- purpose tags what verifying the code/token does (email login, identity link, email
|
||||
-- change, or account deletion); link_token_hash holds the SHA-256 hex of the one-click
|
||||
-- deeplink token (only the hash is stored, mirroring the session-token model).
|
||||
-- Expand-contract: both columns are additive. purpose gets a NOT NULL DEFAULT so
|
||||
-- existing rows fill in; link_token_hash is nullable with a partial-unique index. A
|
||||
-- backend image rollback stays DB-safe — older code simply ignores the new columns. The
|
||||
-- table shape changes, so the generated go-jet model for this table is regenerated.
|
||||
|
||||
-- +goose Up
|
||||
ALTER TABLE backend.email_confirmations
|
||||
ADD COLUMN purpose text NOT NULL DEFAULT 'link',
|
||||
ADD COLUMN link_token_hash text;
|
||||
ALTER TABLE backend.email_confirmations
|
||||
ADD CONSTRAINT email_confirmations_purpose_chk
|
||||
CHECK ((purpose = ANY (ARRAY['login'::text, 'link'::text, 'change'::text, 'delete'::text])));
|
||||
CREATE UNIQUE INDEX email_confirmations_link_token_hash_key
|
||||
ON backend.email_confirmations (link_token_hash)
|
||||
WHERE link_token_hash IS NOT NULL;
|
||||
|
||||
-- +goose Down
|
||||
DROP INDEX IF EXISTS backend.email_confirmations_link_token_hash_key;
|
||||
ALTER TABLE backend.email_confirmations
|
||||
DROP CONSTRAINT IF EXISTS email_confirmations_purpose_chk;
|
||||
ALTER TABLE backend.email_confirmations
|
||||
DROP COLUMN IF EXISTS link_token_hash,
|
||||
DROP COLUMN IF EXISTS purpose;
|
||||
Reference in New Issue
Block a user