start dev

This commit is contained in:
2026-01-18 08:19:01 -06:00
parent 93cfd87821
commit 144cd4787e
18 changed files with 837 additions and 32 deletions
+80
View File
@@ -0,0 +1,80 @@
import type { User } from '$lib/types';
import bcrypt from 'bcrypt';
import sql from '$lib/db/db.server';
import type { Cookies } from '@sveltejs/kit';
import jwt from 'jsonwebtoken';
export function setJWT(cookies: Cookies, user: User) {
const payload = {
id: user.id,
email: user.email,
name: user.name
};
if (process.env.JWT_SECRET === undefined) {
throw new Error('JWT_SECRET not defined');
}
if (process.env.BASE_URL === undefined) {
throw new Error('BASE_URL not defined');
}
const secure: boolean = process.env.BASE_URL?.includes('https://');
const maxAge = 60 * 60 * 24 * 30; // 30 days
const JWT = jwt.sign(payload, process.env.JWT_SECRET, { expiresIn: '30d' });
cookies.set('jwt', JWT, { maxAge, path: '/', httpOnly: false, secure });
}
// export function checkPerms(cookies: Cookies, perms: number): void {
// const JWT = cookies.get('jwt');
// if (!JWT) throw error(403, 'Unauthorized');
// if (process.env.JWT_SECRET === undefined) {
// throw new Error('JWT_SECRET not defined');
// }
// const user = jwt.verify(JWT, process.env.JWT_SECRET) as User;
// if ((user.perms & perms) !== perms) {
// throw error(403, 'Unauthorized');
// }
// }
//
// export function hasPerms(cookies: Cookies, perms: number): boolean {
// const JWT = cookies.get('jwt');
// if (!JWT) return false;
// if (process.env.JWT_SECRET === undefined) {
// throw new Error('JWT_SECRET not defined');
// }
// const user = jwt.verify(JWT, process.env.JWT_SECRET) as User;
// return (user.perms & perms) === perms;
// }
export async function login(email: string, password: string): Promise<User> {
try {
const [user] = await sql`
SELECT id, email, password_hash, perms, name
WHERE email = ${email};
`;
if (await bcrypt.compare(password, user.password_hash)) {
delete user.password_hash;
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
sql`
UPDATE users
SET last_signin = NOW()
WHERE id = ${user.id};
`;
return <User>user;
}
} catch {
throw Error('Error signing in ');
}
throw Error('Invalid email or password');
}
// await createUser(<User>{
// email: 'drake@marinodev.com',
// password: 'password',
// perms: 255,
// name: 'Drake'
// });
+9
View File
@@ -0,0 +1,9 @@
export const PERMISSIONS = {
VIEW: 0b00000001,
DO_TASKS: 0b00000010,
MANAGE_USERS: 0b00000100,
MANAGE_ITEMS: 0b00001000,
MANAGE_TASKS: 0b00010000
};
export const EXPIRE_REMINDER_DAYS = 30;
+14
View File
@@ -0,0 +1,14 @@
import postgres from 'postgres';
import * as dotenv from 'dotenv';
dotenv.config({ path: '.env' });
const sql = postgres({
host: process.env.POSTGRES_HOST,
port: parseInt(process.env.POSTGRES_PORT!),
database: process.env.POSTGRES_DB,
username: process.env.POSTGRES_USER,
password: process.env.POSTGRES_PASSWORD
});
export default sql;
+67
View File
@@ -0,0 +1,67 @@
import type { User } from '$lib/types';
import sql from '$lib/db/db.server';
import bcrypt from 'bcrypt';
// should require MANAGE_USERS permission
export async function getUsers(searchQuery: string | null = null): Promise<User[]> {
return sql`
SELECT id,
email,
perms,
name,
created_at AT TIME ZONE 'UTC' AS "createdAt",
last_signin AT TIME ZONE 'UTC' AS "lastSignIn"
FROM users
WHERE (${!searchQuery ? sql`TRUE` : sql`email ILIKE ${'%' + searchQuery + '%'} OR name ILIKE ${'%' + searchQuery + '%'}`});
`;
}
export async function getUser(id: number): Promise<User> {
return <User>(
await sql`
SELECT id,
email,
perms,
name,
created_at AT TIME ZONE 'UTC' AS "createdAt",
last_signin AT TIME ZONE 'UTC' AS "lastSignIn"
FROM users
WHERE (id = ${id}) LIMIT 1;
`
)[0];
}
export async function createUser(user: User) {
const passwordHash = await bcrypt.hash(user.password!, 12);
await sql`
INSERT INTO users (email, password_hash, perms, name, created_at, last_signin)
VALUES (${user.email}, ${passwordHash}, ${user.perms}, ${user.name}, NOW(), NOW())
RETURNING id;
`;
}
export async function editUser(user: User) {
let passwordHash: string | undefined;
if (user.password) {
passwordHash = await bcrypt.hash(user.password, 12);
}
await sql`
UPDATE users
SET
email = ${user.email},
${passwordHash ? sql`password_hash = ${passwordHash},` : sql``}
perms = ${user.perms},
name = ${user.name}
WHERE id = ${user.id!}
RETURNING id;
`;
}
export async function deleteUser(id: number) {
await sql`
DELETE FROM users
WHERE id = ${id}
`;
}
+21
View File
@@ -0,0 +1,21 @@
export interface User {
id: number;
email: string;
phone: string;
password?: string;
name: string;
createdAt: Date;
lastSignIn: Date;
}
export interface Item {
id: number;
ownerEmail: string;
ownerPhone: string;
foundDate: Date;
approvedDate?: Date;
claimedDate?: Date;
title: string;
description: string;
transferred: boolean; // to L&F location
}
+41
View File
@@ -0,0 +1,41 @@
import type { User } from '$lib/types';
export const getCookieValue = (name: string): string =>
document.cookie.match('(^|;)\\s*' + name + '\\s*=\\s*([^;]+)')?.pop() || '';
export const getUserFromJWT = (jwt: string): User => JSON.parse(atob(jwt.split('.')[1]));
// export const userData = () => {
// let userData: User | null = null;
// const cookieValue = getCookieValue('jwt');
// if (cookieValue) {
// userData = getUserFromJWT(cookieValue);
// }
// return userData;
// };
export function getFormString(data: FormData, key: string): string | undefined {
const value = data.get(key);
if (value === null) {
return undefined;
}
if (typeof value !== 'string') {
throw Error(`Incorrect input in field ${key}.`);
}
return value.trim();
}
export function getRequiredFormString(data: FormData, key: string) {
const value = data.get(key);
if (typeof value !== 'string') {
throw Error(`Missing required field ${key}.`);
}
return value.trim();
}
export const dateFormatOptions: Intl.DateTimeFormatOptions = {
year: 'numeric',
month: 'short',
day: 'numeric'
};