mirror of
https://github.com/JonasunderscoreJones/accounts.jonasjones.dev.git
synced 2025-10-25 03:49:19 +02:00
base code first iteration
This commit is contained in:
parent
dd22208dfd
commit
bd657fc4cb
1 changed files with 216 additions and 0 deletions
216
src/index.js
Normal file
216
src/index.js
Normal file
|
|
@ -0,0 +1,216 @@
|
|||
const headersCORS = {
|
||||
'Access-Control-Allow-Origin': 'https://dash.jonasjones.dev',
|
||||
'Access-Control-Allow-Methods': 'GET, POST, DELETE, OPTIONS',
|
||||
'Access-Control-Allow-Headers': 'Content-Type, Authorization',
|
||||
'Access-Control-Allow-Credentials': 'true'
|
||||
};
|
||||
|
||||
export default {
|
||||
async fetch(request, env, ctx) {
|
||||
const url = new URL(request.url);
|
||||
const { pathname } = url;
|
||||
|
||||
if (request.method === 'OPTIONS') {
|
||||
return new Response(null, { status: 200, headers: headersCORS });
|
||||
}
|
||||
|
||||
// Router
|
||||
if (pathname === '/') {
|
||||
// redirect to account dashboard page
|
||||
return new Response(null, {
|
||||
status: 302,
|
||||
headers: { Location: 'https://dash.jonasjones.dev', },
|
||||
});
|
||||
} else if (pathname === '/login' && request.method === 'POST') {
|
||||
return handleLogin(request, env);
|
||||
} else if (pathname === '/register' && request.method === 'POST') {
|
||||
return handleRegister(request, env);
|
||||
} else if (pathname === '/logout' && request.method === 'POST') {
|
||||
return handleLogout(request, env);
|
||||
} else if (pathname === '/account' && request.method === 'GET') {
|
||||
return getAccountData(request, env);
|
||||
} else if (pathname === '/account' && request.method === 'POST') {
|
||||
return updateAccountData(request, env);
|
||||
} else if (pathname === '/session' && request.method === 'GET') {
|
||||
return sessionHealthCheck(request, env);
|
||||
} else {
|
||||
return new Response('Not Found', { status: 404, headers: headersCORS });
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
// Helpers
|
||||
async function handleLogin(request, env) {
|
||||
const { email, password } = await request.json();
|
||||
const db = env.DB;
|
||||
|
||||
if (!email || !password) {
|
||||
return new Response('Bad Request', { status: 400, headers: headersCORS });
|
||||
}
|
||||
|
||||
// Check user exists and validate password
|
||||
const userQuery = `
|
||||
SELECT id, passwordhash, is_active FROM users WHERE email = ?;
|
||||
`;
|
||||
const user = await db.prepare(userQuery).bind(email).first();
|
||||
|
||||
if (!user || !user.is_active) {
|
||||
return new Response('Invalid email or account inactive.', { status: 403, headers: headersCORS });
|
||||
}
|
||||
|
||||
const validPassword = await verifyPassword(password, user.passwordhash);
|
||||
if (!validPassword) {
|
||||
return new Response('Invalid credentials.', { status: 403, headers: headersCORS });
|
||||
}
|
||||
|
||||
// Create session
|
||||
const sessionKey = crypto.randomUUID();
|
||||
const created = formatDate(new Date());
|
||||
const expiration = formatDate(new Date(Date.now() + 7 * 24 * 60 * 60 * 1000)); // 7 days later
|
||||
|
||||
//TODO: Update last_login field in users table
|
||||
//TODO: Add entry to user_actions table for login, signup, etc.
|
||||
|
||||
const sessionInsert = `
|
||||
INSERT INTO sessions (created, expiration, userid, sessionkey)
|
||||
VALUES (?, ?, ?, ?);
|
||||
`;
|
||||
await db.prepare(sessionInsert).bind(created, expiration, user.id, sessionKey).run();
|
||||
|
||||
return new Response(JSON.stringify({ sessionKey, expiration }), { status: 200, headers: headersCORS });
|
||||
}
|
||||
|
||||
async function handleRegister(request, env) {
|
||||
const { username, password, email, first_name, last_name } = await request.json();
|
||||
const db = env.DB;
|
||||
|
||||
if (!username || !password, !email || !first_name || !last_name) {
|
||||
return new Response('Bad Request', { status: 400, headers: headersCORS });
|
||||
}
|
||||
|
||||
// Hash password
|
||||
const passwordHash = await hashPassword(password);
|
||||
console.log(password, passwordHash)
|
||||
|
||||
try {
|
||||
const insertUser = `
|
||||
INSERT INTO users (created, username, passwordhash, email, first_name, last_name)
|
||||
VALUES (?, ?, ?, ?, ?, ?);
|
||||
`;
|
||||
const created = formatDate(new Date());
|
||||
await db.prepare(insertUser).bind(created, username, passwordHash, email, first_name, last_name).run();
|
||||
|
||||
return new Response('User registered successfully.', { status: 201, headers: headersCORS });
|
||||
} catch (error) {
|
||||
if (error.message.includes('UNIQUE')) {
|
||||
return new Response('Username or email already exists.', { status: 409, headers: headersCORS });
|
||||
}
|
||||
console.log(error.message)
|
||||
return new Response('Error registering user.', { status: 500, headers: headersCORS });
|
||||
}
|
||||
}
|
||||
|
||||
async function handleLogout(request, env) {
|
||||
const { sessionKey } = await request.json();
|
||||
const db = env.DB;
|
||||
|
||||
const deleteSession = `
|
||||
DELETE FROM sessions WHERE sessionkey = ?;
|
||||
`;
|
||||
await db.prepare(deleteSession).bind(sessionKey).run();
|
||||
|
||||
return new Response('Logged out successfully.', { status: 200, headers: headersCORS });
|
||||
}
|
||||
|
||||
async function getAccountData(request, env) {
|
||||
const sessionKey = request.headers.get('Authorization')?.replace('Bearer ', '');
|
||||
if (!sessionKey) {
|
||||
return new Response('Bad Request', { status: 400, headers: headersCORS });
|
||||
}
|
||||
|
||||
const db = env.DB;
|
||||
|
||||
const accountQuery = `
|
||||
SELECT u.username, u.email, u.first_name, u.last_name
|
||||
FROM users u
|
||||
JOIN sessions s ON u.id = s.userid
|
||||
WHERE s.sessionkey = ?;
|
||||
`;
|
||||
const account = await db.prepare(accountQuery).bind(sessionKey).first();
|
||||
|
||||
if (!account) {
|
||||
return new Response('Unauthorized', { status: 401, headers: headersCORS });
|
||||
}
|
||||
|
||||
return new Response(JSON.stringify(account), { status: 200, headers: headersCORS });
|
||||
}
|
||||
|
||||
async function updateAccountData(request, env) {
|
||||
const sessionKey = request.headers.get('Authorization')?.replace('Bearer ', '');
|
||||
const { username, password, email, first_name, last_name } = await request.json();
|
||||
const db = env.DB;
|
||||
|
||||
if (!username || !email || !first_name || !last_name || !password) {
|
||||
return new Response('Bad Request', { status: 400, headers: headersCORS });
|
||||
}
|
||||
|
||||
const passwordHash = await hashPassword(password);
|
||||
|
||||
const updateAccount = `
|
||||
UPDATE users
|
||||
SET username = ?, passwordhash = ?, email = ?, first_name = ?, last_name = ?
|
||||
WHERE id = (
|
||||
SELECT userid FROM sessions WHERE sessionkey = ?
|
||||
);
|
||||
`;
|
||||
await db.prepare(updateAccount).bind(username, passwordHash, email, first_name, last_name, sessionKey).run();
|
||||
|
||||
return new Response('Account updated successfully.', { status: 200, headers: headersCORS });
|
||||
}
|
||||
|
||||
async function sessionHealthCheck(request, env) {
|
||||
const sessionKey = request.headers.get('Authorization')?.replace('Bearer ', '');
|
||||
if (!sessionKey) {
|
||||
return new Response('Bad Request', { status: 400, headers: headersCORS });
|
||||
}
|
||||
|
||||
const db = env.DB;
|
||||
|
||||
const sessionQuery = `
|
||||
SELECT expiration FROM sessions WHERE sessionkey = ?;
|
||||
`;
|
||||
const session = await db.prepare(sessionQuery).bind(sessionKey).first();
|
||||
|
||||
if (!session) {
|
||||
return new Response(JSON.stringify({ valid: false, error: 'Invalid sessionKey.' }), { status: 401, headers: headersCORS });
|
||||
}
|
||||
|
||||
if (new Date(session.expiration) < new Date()) {
|
||||
return new Response(JSON.stringify({ valid: false, error: 'Session expired.' }), { status: 401, headers: headersCORS });
|
||||
}
|
||||
|
||||
return new Response(JSON.stringify({ valid: true, userId: session.userid }), { status: 200, headers: headersCORS });
|
||||
}
|
||||
|
||||
// Utility functions
|
||||
async function hashPassword(password) {
|
||||
const encoder = new TextEncoder();
|
||||
const data = encoder.encode(password);
|
||||
|
||||
const hashBuffer = await crypto.subtle.digest('SHA-256', data);
|
||||
return bufferToHex(hashBuffer);
|
||||
}
|
||||
|
||||
async function verifyPassword(password, storedHash) {
|
||||
const hash = await hashPassword(password);
|
||||
return hash === storedHash;
|
||||
}
|
||||
|
||||
function bufferToHex(buffer) {
|
||||
const byteArray = new Uint8Array(buffer);
|
||||
return Array.from(byteArray, byte => byte.toString(16).padStart(2, '0')).join('');
|
||||
}
|
||||
|
||||
function formatDate(date) {
|
||||
return date.toISOString().replace('T', ' ').split('.')[0]; // Converts to 'YYYY-MM-DD HH:MM:SS'
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue