Some checks failed
PR Checks / lint-test-and-docker-build (pull_request) Failing after 23s
68 lines
2.1 KiB
TypeScript
68 lines
2.1 KiB
TypeScript
import { env } from '$env/dynamic/private';
|
|
import argon2 from 'argon2';
|
|
import { db } from './db/index.js';
|
|
|
|
export interface LocalCredentialRecord {
|
|
userId: string;
|
|
username: string;
|
|
fullName: string;
|
|
email: string | null;
|
|
passwordHash: string;
|
|
}
|
|
|
|
export const LOCAL_AUTH_MIN_PASSWORD_LENGTH = 12;
|
|
|
|
const getNumberEnv = (value: string | undefined, fallback: number): number => {
|
|
const parsed = Number.parseInt(value ?? '', 10);
|
|
return Number.isFinite(parsed) ? parsed : fallback;
|
|
};
|
|
|
|
export function getLocalCredentialByIdentifier(identifier: string): LocalCredentialRecord | null {
|
|
const normalized = identifier.trim();
|
|
if (!normalized) return null;
|
|
return (
|
|
db.get<LocalCredentialRecord>(
|
|
`SELECT
|
|
users.id as userId,
|
|
users.username as username,
|
|
users.full_name as fullName,
|
|
users.email as email,
|
|
local_credentials.password_hash as passwordHash
|
|
FROM users
|
|
INNER JOIN local_credentials ON local_credentials.user_id = users.id
|
|
WHERE lower(users.username) = lower(?)
|
|
OR (users.email IS NOT NULL AND lower(users.email) = lower(?))
|
|
LIMIT 1`,
|
|
[normalized, normalized]
|
|
) ?? null
|
|
);
|
|
}
|
|
|
|
export async function hashLocalPassword(password: string): Promise<string> {
|
|
const trimmed = password.trim();
|
|
if (trimmed.length < LOCAL_AUTH_MIN_PASSWORD_LENGTH) {
|
|
throw new Error(`Password must be at least ${LOCAL_AUTH_MIN_PASSWORD_LENGTH} characters`);
|
|
}
|
|
const memoryCost = getNumberEnv(env.LOCAL_AUTH_ARGON2_MEMORY_KB, 65536);
|
|
const timeCost = getNumberEnv(env.LOCAL_AUTH_ARGON2_TIME_COST, 3);
|
|
const parallelism = getNumberEnv(env.LOCAL_AUTH_ARGON2_PARALLELISM, 1);
|
|
return argon2.hash(trimmed, {
|
|
type: argon2.argon2id,
|
|
memoryCost,
|
|
timeCost,
|
|
parallelism
|
|
});
|
|
}
|
|
|
|
export async function setLocalCredentialPassword(userId: string, password: string): Promise<void> {
|
|
const passwordHash = await hashLocalPassword(password);
|
|
db.run(
|
|
`INSERT INTO local_credentials (user_id, password_hash)
|
|
VALUES (?, ?)
|
|
ON CONFLICT(user_id) DO UPDATE SET
|
|
password_hash = excluded.password_hash,
|
|
updated_at = datetime('now')`,
|
|
[userId, passwordHash]
|
|
);
|
|
}
|