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
+