+9
.env.example
+9
.env.example
···+DKIM_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----\nYOUR_PRIVATE_KEY_HERE\n-----END PRIVATE KEY-----"
-264
docs/CLASS_SYSTEM_SPEC.md
-264
docs/CLASS_SYSTEM_SPEC.md
···-Restructure Thistle from individual transcript management to class-based transcript organization. Users will manage transcripts grouped by classes, with scheduled meeting times and selective transcription.
+399
index.html
+399
index.html
···+@import url("https://fonts.googleapis.com/css2?family=Courier+Prime:wght@400;700&display=swap");
+70
scripts/send-test-emails.ts
+70
scripts/send-test-emails.ts
···
+144
-4
src/components/auth.ts
+144
-4
src/components/auth.ts
··················+${this.needsEmailVerification ? "Verify Email" : this.needsRegistration ? "Create Account" : "Sign In"}···
+34
src/db/schema.ts
+34
src/db/schema.ts
···+CREATE INDEX IF NOT EXISTS idx_verification_tokens_user_id ON email_verification_tokens(user_id);+CREATE INDEX IF NOT EXISTS idx_password_reset_tokens_user_id ON password_reset_tokens(user_id);
+299
-12
src/index.ts
+299
-12
src/index.ts
··················-"Set-Cookie": `session=${sessionId}; HttpOnly; Secure; Path=/; Max-Age=${7 * 24 * 60 * 60}; SameSite=Lax`,·········+"Set-Cookie": `session=${sessionId}; HttpOnly; Secure; Path=/; Max-Age=${7 * 24 * 60 * 60}; SameSite=Lax`,+"Set-Cookie": `session=${sessionId}; HttpOnly; Secure; Path=/; Max-Age=${7 * 24 * 60 * 60}; SameSite=Lax`,···
+131
src/lib/auth.ts
+131
src/lib/auth.ts
···+export function createEmailVerificationToken(userId: number): { code: string; token: string } {
+233
src/lib/email-templates.ts
+233
src/lib/email-templates.ts
···+This code will expire in 24 hours. Enter it in the verification dialog after you login, or click the button below:+<a href="${verifyLink}" class="button" style="display: inline-block; background-color: #ef8354; color: #ffffff; text-decoration: none; padding: 0.75rem 1.5rem; border-radius: 6px; font-weight: 500; font-size: 1rem; margin: 0; border: 2px solid #ef8354;">Verify Email</a>+<p>We received a request to reset your password. Click the button below to create a new password.</p>+<a href="${options.resetLink}" class="button" style="display: inline-block; background-color: #ef8354; color: #ffffff; text-decoration: none; padding: 0.75rem 1.5rem; border-radius: 6px; font-weight: 500; font-size: 1rem; margin: 1rem 0; border: 2px solid #ef8354;">Reset Password</a>+<a href="${options.resetLink}" style="color: #4f5d75; word-break: break-all;">${options.resetLink}</a>+<a href="${options.transcriptLink}" class="button" style="display: inline-block; background-color: #ef8354; color: #ffffff; text-decoration: none; padding: 0.75rem 1.5rem; border-radius: 6px; font-weight: 500; font-size: 1rem; margin: 0; border: 2px solid #ef8354;">View Transcript</a>
+160
src/lib/email-verification.test.ts
+160
src/lib/email-verification.test.ts
···
+100
src/lib/email.ts
+100
src/lib/email.ts
···
+18
src/lib/rate-limit.ts
+18
src/lib/rate-limit.ts
···
+147
src/pages/reset-password.html
+147
src/pages/reset-password.html
···+href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='0.9em' font-size='90'>🪻</text></svg>">+<div class="reset-card" style="background: var(--white); border: 1px solid var(--silver); border-radius: 0.5rem; padding: 2rem;">+<label for="password" style="display: block; margin-bottom: 0.5rem; font-weight: 500;">New Password</label>+style="width: 100%; padding: 0.75rem; border: 1px solid var(--silver); border-radius: 0.25rem; font-size: 1rem;"+<label for="confirm-password" style="display: block; margin-bottom: 0.5rem; font-weight: 500;">Confirm Password</label>+style="width: 100%; padding: 0.75rem; border: 1px solid var(--silver); border-radius: 0.25rem; font-size: 1rem;"+<div id="error-message" style="display: none; color: var(--coral); margin-bottom: 1rem; padding: 0.75rem; background: #fef2f2; border-radius: 0.25rem;"></div>+style="width: 100%; padding: 0.75rem; background: var(--accent); color: var(--white); border: none; border-radius: 0.25rem; font-size: 1rem; font-weight: 500; cursor: pointer;"+<a href="/" style="color: var(--accent); font-weight: 500; text-decoration: none;">Go to home</a>