Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
ca3016d
feat(auth): :truck: create router file
JasonWarrenUK Mar 17, 2025
f81790f
feat(auth): :truck: create 4 placeholder routes for auth functions
JasonWarrenUK Mar 17, 2025
8c6e80a
feat(auth): :truck: add placeholder comments to routes
JasonWarrenUK Mar 17, 2025
431f565
feat(auth): :passport_control: add detailed authentication plan
JasonWarrenUK Mar 20, 2025
c809e03
feat(auth): update implementation plan to use better-auth
JasonWarrenUK Mar 20, 2025
85c029f
feat(auth): implement initial authentication components
JasonWarrenUK Mar 20, 2025
1198190
feat(auth): implement initial authentication components
JasonWarrenUK Mar 20, 2025
8a5b91e
test: add comprehensive test suite for authentication components
JasonWarrenUK Mar 20, 2025
91efb96
fix(auth): implement temporary auth handlers to resolve runtime errors
JasonWarrenUK Mar 20, 2025
e27bff6
fix(auth): implement temporary auth handlers to resolve runtime errors
JasonWarrenUK Mar 21, 2025
8bb4a6d
feat(auth): implement better-auth library integration
JasonWarrenUK Mar 21, 2025
5c5aa34
tasklist
JasonWarrenUK Mar 21, 2025
92aeb53
fix(auth): improve better-auth integration with robust detection…
JasonWarrenUK Mar 21, 2025
626d742
fix(auth): implement handler-based better-auth integration…
JasonWarrenUK Mar 21, 2025
331f0ab
feat(auth): :construction: Yet more attempts to correctly call the f*…
JasonWarrenUK Mar 25, 2025
90478eb
feat(auth): :construction: still going...
JasonWarrenUK Mar 25, 2025
bfabd86
refactor(logs): :loud_sound: hide logs behind logger bool
JasonWarrenUK Mar 28, 2025
df46ce8
feat(data): :card_file_box: create SQLite instance for users
JasonWarrenUK Mar 28, 2025
61b3328
feat(data): :card_file_box: create supabase & kysely instances
JasonWarrenUK Mar 30, 2025
eb16434
feat(data): :card_file_box: create supabase->kysely type converter
JasonWarrenUK Mar 31, 2025
d77b2d8
feat(auth): :passport_control: restart authentication build using Sup…
JasonWarrenUK Apr 1, 2025
3b090ae
feat(auth): :sparkles: run verification middleware for http requests
JasonWarrenUK Apr 1, 2025
60ac990
refactor(auth): :coffin: remove better-auth & kysely code
JasonWarrenUK Apr 1, 2025
110243f
fix(auth): :truck: update redirect callback
JasonWarrenUK Apr 1, 2025
41df0b7
chore(server): :technologist: update local port
JasonWarrenUK Apr 1, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion api/neo4j/find.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import neo4j, { Driver } from "neo4j";
import { creds as c } from "../../utils/auth/neo4jCred.ts";
import { creds as c } from "utils/creds/neo4jCred.ts";

export async function findUserById( authId: string, publicOnly: boolean = true ):Promise<string[]> {
console.group(`|=== findUserById() ===`);
Expand Down
2 changes: 1 addition & 1 deletion api/neo4j/get.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import neo4j, { Driver } from "neo4j";
import { creds as c } from "../../utils/auth/neo4jCred.ts";
import { creds as c } from "utils/creds/neo4jCred.ts";

export async function getNouns() {
let driver: Driver | null = null;
Expand Down
2 changes: 1 addition & 1 deletion api/neo4j/reset.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import neo4j, { Driver } from "neo4j";
import { creds as c } from "../../utils/auth/neo4jCred.ts";
import { creds as c } from "utils/creds/neo4jCred.ts";

export async function reset() {
let driver: Driver, result;
Expand Down
2 changes: 1 addition & 1 deletion api/neo4j/seed.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import neo4j, { Driver } from "neo4j";
import { creds } from "../../utils/auth/neo4jCred.ts";
import { creds } from "utils/creds/neo4jCred.ts";

const data = JSON.parse(
await Deno.readTextFile("./data/seeds/facSeed.json"),
Expand Down
2 changes: 1 addition & 1 deletion api/neo4j/writeBeacon.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import * as dotenv from "dotenv";
import neo4j, { Driver } from "neo4j";
import type { Lantern, Ember, DBError } from "types/beaconTypes.ts";
import type { Attempt } from "types/serverTypes.ts";
import { creds as c } from "utils/auth/neo4jCred.ts";
import { creds as c } from "utils/creds/neo4jCred.ts";

dotenv.load({ export: true });

Expand Down
93 changes: 93 additions & 0 deletions api/resend/sendMagicLink.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
const resendKey = Deno.env.get("RESEND_KEY");
const isDev = Deno.env.get("DENO_ENV") !== "production";
const FRONTEND_URL = Deno.env.get("FRONTEND_URL") || "http://localhost:3000";

/**
* Sends a magic link email to the specified email address.
* In development mode, it will not send an actual email and just log the URL.
*
* @param email The email address to send the magic link to
* @param magicLinkUrl The full magic link URL including token
* @returns Promise<void> to match Better Auth's expected return type
*/
export async function sendMagicLinkEmail(
email: string,
magicLinkUrl: string
): Promise<void> {
try {
console.group("|=== sendMagicLinkEmail() ===|");
console.info("| Parameters");
console.table([
{ is: "email", value: email },
{ is: "magicLinkUrl", value: magicLinkUrl },
]);

// Extract token from URL for testing purposes
const urlObj = new URL(magicLinkUrl);
const token = urlObj.searchParams.get("token") || "unknown-token";

// Always log the token and URL during development
if (isDev) {
console.groupCollapsed("|=== 🧪 DEVELOPMENT MODE ===|");
console.log(`| 📧 Would send magic link to: ${email}`);
console.log(`| 🔗 Magic Link URL: ${magicLinkUrl}`);
console.log(`| 🔑 Token: ${token}`);

// Generate a test verification URL for easier testing
const verifyUrl = `${FRONTEND_URL}/api/auth/verify?token=${token}`;
console.log(`| 🔍 Verification URL: ${verifyUrl}`);
console.groupEnd();

console.groupEnd();
return;
}

// In production, send actual email via Resend
if (!resendKey) {
console.error("| ❌ RESEND_KEY environment variable is not set");
console.groupEnd();
return;
}

const res = await fetch("https://api.resend.com/emails", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${resendKey}`,
},
body: JSON.stringify({
from: "LIFT <auth@beacons.ink>",
to: `<${email}>`,
subject: "Sign in to Beacons",
html: `
<div style="font-family: Arial, sans-serif; max-width: 600px; margin: 0 auto; padding: 20px; border: 1px solid #e0e0e0; border-radius: 5px;">
<h1 style="color: #333; text-align: center;">Sign in to Beacons</h1>
<p style="color: #555; font-size: 16px;">Click the link below to sign in:</p>
<div style="text-align: center; margin: 30px 0;">
<a href="${magicLinkUrl}" style="background-color: #4361EE; color: white; padding: 12px 24px; text-decoration: none; border-radius: 4px; font-weight: bold;">Sign In to Beacons</a>
</div>
<p style="color: #777; font-size: 14px;">This link will expire in 10 minutes and can only be used once.</p>
<p style="color: #777; font-size: 14px;">If you didn't request this email, you can safely ignore it.</p>
<hr style="border: none; border-top: 1px solid #e0e0e0; margin: 20px 0;">
<p style="color: #999; font-size: 12px; text-align: center;">© LIFT Beacons ${new Date().getFullYear()}</p>
</div>
`,
}),
});

if (res.ok) {
console.info("| ✅ Magic link email sent successfully");
console.groupEnd();
return;
} else {
const errorData = await res.text();
console.warn(`| ❌ Error from Resend API: ${errorData}`);
console.groupEnd();
return;
}
} catch (error) {
console.error("| ❌ Error sending magic link:", error);
console.groupEnd();
return;
}
}
121 changes: 63 additions & 58 deletions deno.jsonc
Original file line number Diff line number Diff line change
@@ -1,74 +1,78 @@
{
"tasks": {
"dev": {
"runDev": {
"description": "Run the local development build",
"command": "deno run -A --watch --unstable-cron --env-file=.env.local main.ts"
},
"prod": {
"runProd": {
"description": "Run the local production build",
"command": "deno run -A --watch --unstable-cron --env-file=.env.production main.ts"
},
// Dev Tools
"test": {
"description": "Run the tests",
"command": "deno test -A"
},
"test": {
"description": "Run the tests",
"command": "deno test -A --unstable-kv --no-check"
},
"testAuth": {
"description": "Run only authentication tests",
"command": "deno test -A --unstable-kv --no-check tests/auth-test.ts"
},
// Resend API
"checkResend": {
"description": "Tests the Resend API",
"command": "deno run -A --watch --unstable-cron --env-file=.env.local ./utils/emails/sendTest.ts"
},
"resendCheck": {
"description": "Tests the Resend API",
"command": "deno run -A --watch --unstable-cron --env-file=.env.local ./utils/emails/sendTest.ts"
},
// Neo4j API
"dbSeedLocal": {
"description": "Seed the local instance of neo4j",
"command": "deno run -A --env-file=.env.local queries/seed.ts"
},
"dbSeedProd": {
"description": "Seed the production instance of neo4j",
"command": "deno run -A --env-file=.env.production queries/seed.ts"
},
"dbResetLocal": {
"description": "Reset the local instance of neo4j",
"command": "deno run -A --env-file=.env.local queries/reset.ts"
},
"dbResetProduction": {
"description": "Reset the production instance of neo4j",
"command": "deno run -A --env-file=.env.production queries/reset.ts"
}
"n4jSeedL": {
"description": "Seed the local instance of neo4j",
"command": "deno run -A --env-file=.env.local queries/seed.ts"
},
"n4jSeedP": {
"description": "Seed the production instance of neo4j",
"command": "deno run -A --env-file=.env.production queries/seed.ts"
},
"n4jResetL": {
"description": "Reset the local instance of neo4j",
"command": "deno run -A --env-file=.env.local queries/reset.ts"
},
"n4jResetP": {
"description": "Reset the production instance of neo4j",
"command": "deno run -A --env-file=.env.production queries/reset.ts"
},
// Auth
"auth": {
"description": "Run the authentication workflow",
"command": "deno run -A --env-file=.env.local utils/auth.ts"
}
},
"imports": {
// Package Imports
/* Hotlink */
"oak": "https://deno.land/x/oak@v17.1.4/mod.ts",
/* JSR Packages */
"dotenv": "jsr:@std/dotenv",
/* NPM Packages */
"compromise": "npm:compromise@14.10.0",
"neo4j": "npm:neo4j-driver@^5.27.0",
"zod": "npm:zod",
// Path Mapping
/* API Functions */
"api/": "./api/",
"neo4jApi/": "./api/neo4j/",
"resendApi/": "./api/resend/",
"content/": "./content/",
"data": "./data/",
/* Routers */
"routes/": "./routes/",
"dbRoutes/": "./routes/neo4j/",
"emailRoutes/": "./routes/resend/",
/* Utility Functions */
"utils/": "./utils/",
"credUtils/": "./utils/creds/",
"dbUtils/": "./utils/db/",
"devUtils/": "./utils/dev/",
"langUtils/": "./utils/lang/",
"types/": "./types/"
// JSR
"oak": "jsr:@oak/oak",
"dotenv": "jsr:@std/dotenv",
// NPM
"compromise": "npm:compromise@14.10.0",
"neo4j": "npm:neo4j-driver@^5.27.0",
"supabase": "jsr:@supabase/supabase-js@2",
"zod": "npm:zod",
// Filepath
"api/": "./api/",
"authApi/": "./api/auth/",
"neo4jApi/": "./api/neo4j/",
"resendApi/": "./api/resend/",
"content/": "./content/",
"data": "./data/",
"routes/": "./routes/",
"authRoutes/": "./routes/authRoutes/",
"dbRoutes/": "./routes/dbRoutes/",
"emailRoutes/": "./routes/emailRoutes/",
"utils/": "./utils/",
"credUtils/": "./utils/creds/",
"dbUtils/": "./utils/db/",
"devUtils/": "./utils/dev/",
"langUtils/": "./utils/lang/",
"types/": "./types/"
},
"unstable": [ /* Unstable Features */
"cron", /* Cron Jobs */
"kv" /* Key-Value Store */
],
"unstable": [ "cron", "kv" ],
"fmt": {
"semiColons": true,
"singleQuote": false,
Expand All @@ -87,7 +91,8 @@
"include": [],
"exclude": [
"ban-untagged-todo",
"no-unused-vars"
"no-unused-vars",
"no-explicit-any"
]
}
}
Expand Down
Loading