Skip to content

Commit

Permalink
make signup and password reset token time customizable (#56)
Browse files Browse the repository at this point in the history
* make signup and password reset token time customizable

* add token time env var to readme
  • Loading branch information
cmintey authored Oct 17, 2023
1 parent d8beb8a commit 9c0172b
Show file tree
Hide file tree
Showing 11 changed files with 66 additions and 41 deletions.
20 changes: 2 additions & 18 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,21 +1,5 @@
# If behind a reverse proxy, set this to your domain
# i.e. https://wishlist.your-domain.org
ORIGIN=

ENABLE_SIGNUP=true

ALLOW_SUGGESTIONS=true # turns this on or off
# methods include
# - suprise: automatically add suggested item and hide from the suggestee
# - auto-approval: automatically add suggested item, but show to suggestee and allow removal by suggestee
# - approval: require approval of suggested items by the suggestee
SUGGESTION_METHOD="approval"

# Set these to use email for sending reset password links
# and invite codes
SMTP_HOST=
SMTP_PORT=
SMTP_USER=
SMTP_PASS=
SMTP_FROM=
SMTP_FROM_NAME=
# Hours until signup and password reset tokens expire
TOKEN_TIME=72
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ services:
- ./data:/usr/src/app/data
environment:
ORIGIN: https://wishlist.example.com
TOKEN_TIME: 72 # hours until signup and password reset tokens expire
```

Then simply run `docker compose up -d`.
Expand Down
28 changes: 28 additions & 0 deletions prisma/migrations/20231017150829_remove_expires_in/migration.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
-- RedefineTables
PRAGMA foreign_keys=OFF;
CREATE TABLE "new_password_resets" (
"id" TEXT NOT NULL PRIMARY KEY,
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
"userId" TEXT NOT NULL,
"hashedToken" TEXT NOT NULL,
"redeemed" BOOLEAN NOT NULL DEFAULT false
);
INSERT INTO "new_password_resets" ("createdAt", "hashedToken", "id", "redeemed", "userId") SELECT "createdAt", "hashedToken", "id", "redeemed", "userId" FROM "password_resets";
DROP TABLE "password_resets";
ALTER TABLE "new_password_resets" RENAME TO "password_resets";
CREATE UNIQUE INDEX "password_resets_id_key" ON "password_resets"("id");
CREATE INDEX "password_resets_hashedToken_idx" ON "password_resets"("hashedToken");
CREATE TABLE "new_signup_tokens" (
"id" TEXT NOT NULL PRIMARY KEY,
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
"hashedToken" TEXT NOT NULL,
"redeemed" BOOLEAN NOT NULL DEFAULT false,
"groupId" TEXT NOT NULL DEFAULT 'global'
);
INSERT INTO "new_signup_tokens" ("createdAt", "groupId", "hashedToken", "id", "redeemed") SELECT "createdAt", "groupId", "hashedToken", "id", "redeemed" FROM "signup_tokens";
DROP TABLE "signup_tokens";
ALTER TABLE "new_signup_tokens" RENAME TO "signup_tokens";
CREATE UNIQUE INDEX "signup_tokens_id_key" ON "signup_tokens"("id");
CREATE INDEX "signup_tokens_hashedToken_idx" ON "signup_tokens"("hashedToken");
PRAGMA foreign_key_check;
PRAGMA foreign_keys=ON;
2 changes: 0 additions & 2 deletions prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,6 @@ model Item {
model PasswordReset {
id String @id @unique @default(uuid())
createdAt DateTime @default(now())
expiresIn Int
userId String
hashedToken String
redeemed Boolean @default(false)
Expand All @@ -113,7 +112,6 @@ model PasswordReset {
model SignupToken {
id String @id @unique @default(uuid())
createdAt DateTime @default(now())
expiresIn Int
hashedToken String
redeemed Boolean @default(false)
groupId String @default("global")
Expand Down
9 changes: 8 additions & 1 deletion src/lib/components/BackButton.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,14 @@
import logo from "$lib/assets/logo.png";
export let label = "Back";
const disabledUrls = ["/login", "/signup", "/"];
const disabledUrls = [
"/login",
"/signup",
"/",
"/forgot-password",
"/reset-password",
"/group-error"
];
let documentTitle: string | undefined;
let disabled = true;
Expand Down
4 changes: 1 addition & 3 deletions src/lib/server/invite-user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,7 @@ export const inviteUser = async ({ url, request }: RequestEvent) => {
if (!config.smtp.enable) {
await client.signupToken.create({
data: {
hashedToken: hashToken(token),
expiresIn: 21600000 // 6 hours in milliseconds
hashedToken: hashToken(token)
}
});

Expand Down Expand Up @@ -43,7 +42,6 @@ export const inviteUser = async ({ url, request }: RequestEvent) => {
await client.signupToken.create({
data: {
hashedToken: hashToken(token),
expiresIn: 21600000, // 6 hours in milliseconds
groupId: emailData.data["invite-group"]
}
});
Expand Down
14 changes: 13 additions & 1 deletion src/routes/+layout.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import "../app.postcss";
import { afterNavigate, beforeNavigate } from "$app/navigation";
import { page } from "$app/stores";
import {
AppShell,
Modal,
Expand All @@ -25,8 +26,18 @@
export let data: LayoutData;
const titleDisabledUrls = [
"/login",
"/signup",
"/",
"/forgot-password",
"/reset-password",
"/group-error"
];
let showNavigationLoadingBar = false;
let documentTitle: string | undefined;
let disabled = false;
beforeNavigate(() => {
showNavigationLoadingBar = true;
Expand All @@ -35,6 +46,7 @@
afterNavigate(() => {
showNavigationLoadingBar = false;
documentTitle = document?.title;
disabled = titleDisabledUrls.find((url) => $page.url.pathname === url) !== undefined;
});
initializeStores();
Expand Down Expand Up @@ -89,7 +101,7 @@
</svelte:fragment>
<!-- Router Slot -->
<div class="px-4 py-4 md:px-12 lg:px-32 xl:px-56">
{#if !$isInstalled && documentTitle}
{#if !$isInstalled && !disabled && documentTitle}
<h1 class="h1 pb-2 md:pb-4">{documentTitle}</h1>
{/if}
<slot />
Expand Down
3 changes: 1 addition & 2 deletions src/routes/admin/users/[username]/+page.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,7 @@ export const actions: Actions = {
await client.passwordReset.create({
data: {
userId: user.id,
hashedToken: hashToken(token),
expiresIn: 21600000 // 6 hours in milliseconds
hashedToken: hashToken(token)
}
});

Expand Down
6 changes: 2 additions & 4 deletions src/routes/forgot-password/+page.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,14 +50,12 @@ export const actions: Actions = {
await client.passwordReset.create({
data: {
userId: user.id,
hashedToken: hashToken(token),
expiresIn: 21600000 // 6 hours in milliseconds
hashedToken: hashToken(token)
}
});

if (config.smtp.enable && user.email) {
const sent = await sendPasswordReset(user.email, tokenUrl.href);
console.log(`email sent: ${sent}`);
await sendPasswordReset(user.email, tokenUrl.href);
}
}

Expand Down
10 changes: 5 additions & 5 deletions src/routes/reset-password/+page.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { resetPasswordSchema } from "$lib/validations";
import { error, fail } from "@sveltejs/kit";
import { z } from "zod";
import type { Actions, PageServerLoad } from "./$types";
import { env } from "$env/dynamic/private";

export const load: PageServerLoad = async ({ request }) => {
const url = new URL(request.url);
Expand All @@ -19,16 +20,15 @@ export const load: PageServerLoad = async ({ request }) => {
select: {
id: true,
userId: true,
createdAt: true,
expiresIn: true
createdAt: true
}
});

if (!reset) throw error(400, "reset token not found");

const createdAt = reset.createdAt;
const expiry = createdAt.getTime() + reset.expiresIn;
if (expiry - Date.now() > 0) {
const expiresIn = (env.TOKEN_TIME ? Number.parseInt(env.TOKEN_TIME) : 72) * 3600000;
const expiry = reset.createdAt.getTime() + expiresIn;
if (Date.now() < expiry) {
return { valid: true, userId: reset.userId, id: reset.id };
}
}
Expand Down
10 changes: 5 additions & 5 deletions src/routes/signup/+page.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import type { Actions, PageServerLoad } from "./$types";
import { hashToken } from "$lib/server/token";
import { getConfig } from "$lib/server/config";
import { Role } from "$lib/schema";
import { env } from "$env/dynamic/private";

export const load: PageServerLoad = async ({ locals, request }) => {
// If the user session exists, redirect authenticated users to the profile page.
Expand All @@ -23,16 +24,15 @@ export const load: PageServerLoad = async ({ locals, request }) => {
},
select: {
id: true,
createdAt: true,
expiresIn: true
createdAt: true
}
});

if (!signup) throw error(400, "reset token not found");

const createdAt = signup.createdAt;
const expiry = createdAt.getTime() + signup.expiresIn;
if (expiry - Date.now() > 0) {
const expiresIn = (env.TOKEN_TIME ? Number.parseInt(env.TOKEN_TIME) : 72) * 3600000;
const expiry = signup.createdAt.getTime() + expiresIn;
if (Date.now() < expiry) {
return { valid: true, id: signup.id };
}
throw error(400, "Invite code is either invalid or already been used");
Expand Down

0 comments on commit 9c0172b

Please sign in to comment.