diff --git a/Resources/Database/Create.sql b/Resources/Database/Create.sql index 6a62445..e4cec1f 100644 --- a/Resources/Database/Create.sql +++ b/Resources/Database/Create.sql @@ -20,6 +20,7 @@ CREATE TABLE "user_tokens" ( "id" UUID PRIMARY KEY DEFAULT gen_random_uuid(), "user_id" UUID NOT NULL, "token" CHARACTER VARYING (1000) NOT NULL, + "realm" CHARACTER VARYING (10) NOT NULL CHECK ("realm" IN ('invite', 'forgot')), "insert_time" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, CONSTRAINT "user_tokens_user_fk" FOREIGN KEY ("user_id") REFERENCES "users" ("id") ON DELETE CASCADE ); diff --git a/Resources/Database/InitialUser.sql b/Resources/Database/InitialUser.sql index 8cd86b8..e68601d 100644 --- a/Resources/Database/InitialUser.sql +++ b/Resources/Database/InitialUser.sql @@ -13,9 +13,11 @@ WITH "created" AS ( SELECT "id", 'admin' FROM "created" ) -INSERT INTO "user_tokens" ("user_id", "token") -SELECT "id", (SELECT string_agg (substr (c, (random() * length (c) + 1)::integer, 1), '') AS "token" - FROM (VALUES ('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789')) AS x(c), - generate_series (1, 32)) +INSERT INTO "user_tokens" ("user_id", "token", "realm") +SELECT "id", + (SELECT string_agg (substr (c, (random() * length (c) + 1)::integer, 1), '') AS "token" + FROM (VALUES ('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789')) AS x(c), + generate_series (1, 32)), + 'invite' FROM "created" RETURNING 'https://example.com/auth/password/' || "token" AS "Password URL" diff --git a/Sources/ManageableUsers/Models/AdministeredUser.swift b/Sources/ManageableUsers/Models/AdministeredUser.swift index 0ae9ecc..ee452fb 100644 --- a/Sources/ManageableUsers/Models/AdministeredUser.swift +++ b/Sources/ManageableUsers/Models/AdministeredUser.swift @@ -49,8 +49,8 @@ extension ManagedUser { TRUE) RETURNING "id" ) - INSERT INTO "user_tokens" ("user_id", "token") - SELECT "id", \(bind: token) + INSERT INTO "user_tokens" ("user_id", "token", "realm") + SELECT "id", \(bind: token), 'invite' FROM created """) .run() @@ -72,8 +72,8 @@ extension ManagedUser { FROM "created" CROSS JOIN unnest (\(bind: roles)) AS "role_name" ) - INSERT INTO "user_tokens" ("user_id", "token") - SELECT "id", \(bind: token) + INSERT INTO "user_tokens" ("user_id", "token", "realm") + SELECT "id", \(bind: token), 'invite' FROM "created" """) .run() @@ -121,8 +121,8 @@ extension ManagedUser { public static func store (token: String, userId: UUID, on connection: any SQLDatabase) async throws { try await connection.raw(""" - INSERT INTO "user_tokens" ("user_id", "token") - VALUES (\(bind: userId), \(bind: token)) + INSERT INTO "user_tokens" ("user_id", "token", "realm") + VALUES (\(bind: userId), \(bind: token), 'forgot') """) .run() } diff --git a/Sources/ManageableUsers/Models/UserToken.swift b/Sources/ManageableUsers/Models/UserToken.swift index 6b905e6..5fb2fe6 100644 --- a/Sources/ManageableUsers/Models/UserToken.swift +++ b/Sources/ManageableUsers/Models/UserToken.swift @@ -30,7 +30,9 @@ struct UserToken: Decodable { JOIN "users" ON "users"."id" = "user_tokens"."user_id" WHERE "token" = \(bind: token) - AND "insert_time" >= CURRENT_TIMESTAMP - INTERVAL '1 HOUR' + AND "insert_time" >= CURRENT_TIMESTAMP - CASE WHEN "realm" = 'invite' + THEN INTERVAL '1 DAY' + ELSE INTERVAL '1 HOUR' END """) .first (decoding: Token.self) } diff --git a/Tests/SampleAppTests/AuthorizationTests.swift b/Tests/SampleAppTests/AuthorizationTests.swift index 7e65782..f6c5068 100644 --- a/Tests/SampleAppTests/AuthorizationTests.swift +++ b/Tests/SampleAppTests/AuthorizationTests.swift @@ -9,6 +9,8 @@ struct AuthorizationTests { private func withApp(_ test: (Application) async throws -> ()) async throws { let app = try await Application.make (.testing) do { + setenv ("BASE_URL", "http://localhost", 0) + setenv ("EMAIL_SENDER", "nobody@example.com", 0) try await SampleApp.configure (app) let mockDatabase = MockDatabase (eventLoop: app.eventLoopGroup.next()) app.storage[Application.MockDatabaseKey.self] = mockDatabase