From 482ace256f4e58b0b8503aabdd80210c617c9888 Mon Sep 17 00:00:00 2001 From: Randy Dean Date: Fri, 3 Jul 2026 14:32:05 -0400 Subject: [PATCH] Auth: add magic-link/session migrations and runtime datasource - V0004 relaxes members.full_name and adds profile_completed_at so magic-link verification can create shell accounts before sign-up - V0005 creates magic_link_tokens (hashed, single-use, email-keyed) - V0006 creates Spring Session JDBC tables (schema owned by Flyway; spring.session.jdbc.initialize-schema=never) - Re-enable DataSourceAutoConfiguration with spring.datasource wired to the existing DATABASE_* env vars; disable runtime Flyway (migrations stay out-of-band via `just migrate`) - Add spring-session-jdbc, spring-boot-starter-security (previously only transitive via oauth2-client), spring-security-test - New app.auth.* properties (base-url, cookie-secure, magic-link-ttl); dev profile serves non-Secure cookies over plain HTTP - Fix .example.env DATABASE_NAME leftover from codebloom -> patchats Co-Authored-By: Claude Fable 5 --- .example.env | 9 +++++-- ...0004__Relax_members_for_shell_accounts.sql | 9 +++++++ .../V0005__Create_magic_link_tokens_table.sql | 12 +++++++++ .../V0006__Create_spring_session_tables.sql | 26 +++++++++++++++++++ pom.xml | 13 ++++++++++ src/main/resources/application-dev.yml | 4 +++ src/main/resources/application.yml | 23 ++++++++++++++-- 7 files changed, 92 insertions(+), 4 deletions(-) create mode 100644 db/migration/V0004__Relax_members_for_shell_accounts.sql create mode 100644 db/migration/V0005__Create_magic_link_tokens_table.sql create mode 100644 db/migration/V0006__Create_spring_session_tables.sql create mode 100644 src/main/resources/application-dev.yml diff --git a/.example.env b/.example.env index dbdd07c..fd1feb5 100644 --- a/.example.env +++ b/.example.env @@ -2,11 +2,11 @@ DATABASE_HOST=localhost DATABASE_PORT=5432 -DATABASE_NAME=codebloom +DATABASE_NAME=patchats DATABASE_USER=postgres DATABASE_PASSWORD=enterpasswordhere # With the example values, this gets combined inside of the application.properties to make -# jdbc://postgresql://localhost:5432/codebloom?user=postgres&password=enterpasswordhere +# jdbc://postgresql://localhost:5432/patchats?user=postgres&password=enterpasswordhere # SMTP — consumed by spring.mail.* in non-dev profiles (the dev profile logs instead of sending) SMTP_HOST=smtp.example.com @@ -16,3 +16,8 @@ SMTP_PASSWORD=enterpasswordhere # The verified From sender (a real, monitored mailbox on a domain you control) EMAIL_FROM=coffeechats@patinanetwork.org EMAIL_FROM_NAME=PatChats + +# Auth — public origin of the SPA; magic links point at $APP_BASE_URL/auth/verify?token=... +APP_BASE_URL=http://localhost:5173 +# Set to false only when serving over plain HTTP (the dev profile already does this) +AUTH_COOKIE_SECURE=true diff --git a/db/migration/V0004__Relax_members_for_shell_accounts.sql b/db/migration/V0004__Relax_members_for_shell_accounts.sql new file mode 100644 index 0000000..dba6c6b --- /dev/null +++ b/db/migration/V0004__Relax_members_for_shell_accounts.sql @@ -0,0 +1,9 @@ +-- Magic-link auth creates a minimal "shell" member row at link-verification time, +-- before the user has filled in the sign-up form, so full_name can no longer be required. +ALTER TABLE "members" ALTER COLUMN full_name DROP NOT NULL; + +-- Set when the member has completed the sign-up form; NULL marks a shell account. +ALTER TABLE "members" ADD COLUMN profile_completed_at TIMESTAMPTZ; + +-- Existing rows were created through the full sign-up form; treat them as complete. +UPDATE "members" SET profile_completed_at = created_at WHERE full_name IS NOT NULL; diff --git a/db/migration/V0005__Create_magic_link_tokens_table.sql b/db/migration/V0005__Create_magic_link_tokens_table.sql new file mode 100644 index 0000000..a6188d6 --- /dev/null +++ b/db/migration/V0005__Create_magic_link_tokens_table.sql @@ -0,0 +1,12 @@ +-- Single-use sign-in tokens emailed to users. Keyed by email (not member id) +-- because the member row is only created once the link is verified. +CREATE TABLE IF NOT EXISTS "magic_link_tokens" ( + id UUID PRIMARY KEY, + email TEXT NOT NULL, + token_hash TEXT UNIQUE NOT NULL, + expires_at TIMESTAMPTZ NOT NULL, + consumed_at TIMESTAMPTZ, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); + +CREATE INDEX idx_magic_link_tokens_email ON "magic_link_tokens" (email); diff --git a/db/migration/V0006__Create_spring_session_tables.sql b/db/migration/V0006__Create_spring_session_tables.sql new file mode 100644 index 0000000..e3d5702 --- /dev/null +++ b/db/migration/V0006__Create_spring_session_tables.sql @@ -0,0 +1,26 @@ +-- Spring Session JDBC schema, copied verbatim from spring-session-jdbc's +-- org/springframework/session/jdbc/schema-postgresql.sql so Flyway owns it +-- (spring.session.jdbc.initialize-schema is set to "never" in application.yml). +CREATE TABLE SPRING_SESSION ( + PRIMARY_ID CHAR(36) NOT NULL, + SESSION_ID CHAR(36) NOT NULL, + CREATION_TIME BIGINT NOT NULL, + LAST_ACCESS_TIME BIGINT NOT NULL, + MAX_INACTIVE_INTERVAL INT NOT NULL, + EXPIRY_TIME BIGINT NOT NULL, + PRINCIPAL_NAME VARCHAR(100), + CONSTRAINT SPRING_SESSION_PK PRIMARY KEY (PRIMARY_ID) +); + +CREATE UNIQUE INDEX SPRING_SESSION_IX1 ON SPRING_SESSION (SESSION_ID); +CREATE INDEX SPRING_SESSION_IX2 ON SPRING_SESSION (EXPIRY_TIME); +CREATE INDEX SPRING_SESSION_IX3 ON SPRING_SESSION (PRINCIPAL_NAME); + +CREATE TABLE SPRING_SESSION_ATTRIBUTES ( + SESSION_PRIMARY_ID CHAR(36) NOT NULL, + ATTRIBUTE_NAME VARCHAR(200) NOT NULL, + ATTRIBUTE_BYTES BYTEA NOT NULL, + CONSTRAINT SPRING_SESSION_ATTRIBUTES_PK PRIMARY KEY (SESSION_PRIMARY_ID, ATTRIBUTE_NAME), + CONSTRAINT SPRING_SESSION_ATTRIBUTES_FK FOREIGN KEY (SESSION_PRIMARY_ID) + REFERENCES SPRING_SESSION (PRIMARY_ID) ON DELETE CASCADE +); diff --git a/pom.xml b/pom.xml index bd6eddd..3fdf071 100644 --- a/pom.xml +++ b/pom.xml @@ -199,6 +199,19 @@ org.springframework.boot spring-boot-starter-jdbc + + org.springframework.boot + spring-boot-starter-security + + + org.springframework.session + spring-session-jdbc + + + org.springframework.security + spring-security-test + test +