feature(auth): replace clerk.com with custom auth (#103)
* feature(auth): replace clerk.com with custom auth * minor fixes * remove notification preferences * decrease live events interval fix(api): cookies.. # Conflicts: # .gitignore # apps/api/src/index.ts # apps/dashboard/src/app/providers.tsx # packages/trpc/src/trpc.ts
This commit is contained in:
committed by
Carl-Gerhard Lindesvärd
parent
f28802b1c2
commit
d31d9924a5
20
packages/auth/src/cookie.ts
Normal file
20
packages/auth/src/cookie.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import type { ISetCookie } from '@openpanel/validation';
|
||||
import { COOKIE_OPTIONS } from '../constants';
|
||||
|
||||
export function setSessionTokenCookie(
|
||||
setCookie: ISetCookie,
|
||||
token: string,
|
||||
expiresAt: Date,
|
||||
): void {
|
||||
setCookie('session', token, {
|
||||
maxAge: expiresAt.getTime() - new Date().getTime(),
|
||||
...COOKIE_OPTIONS,
|
||||
});
|
||||
}
|
||||
|
||||
export function deleteSessionTokenCookie(setCookie: ISetCookie): void {
|
||||
setCookie('session', '', {
|
||||
maxAge: 0,
|
||||
...COOKIE_OPTIONS,
|
||||
});
|
||||
}
|
||||
4
packages/auth/src/index.ts
Normal file
4
packages/auth/src/index.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export * from './cookie';
|
||||
export * from './oauth';
|
||||
export * from './password';
|
||||
export * from './session';
|
||||
18
packages/auth/src/oauth.ts
Normal file
18
packages/auth/src/oauth.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { GitHub } from 'arctic';
|
||||
|
||||
export type { OAuth2Tokens } from 'arctic';
|
||||
import * as Arctic from 'arctic';
|
||||
|
||||
export { Arctic };
|
||||
|
||||
export const github = new GitHub(
|
||||
process.env.GITHUB_CLIENT_ID ?? '',
|
||||
process.env.GITHUB_CLIENT_SECRET ?? '',
|
||||
process.env.GITHUB_REDIRECT_URI ?? '',
|
||||
);
|
||||
|
||||
export const google = new Arctic.Google(
|
||||
process.env.GOOGLE_CLIENT_ID ?? '',
|
||||
process.env.GOOGLE_CLIENT_SECRET ?? '',
|
||||
process.env.GOOGLE_REDIRECT_URI ?? '',
|
||||
);
|
||||
41
packages/auth/src/password.ts
Normal file
41
packages/auth/src/password.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import { hash, verify } from '@node-rs/argon2';
|
||||
import { sha1 } from '@oslojs/crypto/sha1';
|
||||
import { encodeHexLowerCase } from '@oslojs/encoding';
|
||||
|
||||
export async function hashPassword(password: string): Promise<string> {
|
||||
return await hash(password, {
|
||||
memoryCost: 19456,
|
||||
timeCost: 2,
|
||||
outputLen: 32,
|
||||
parallelism: 1,
|
||||
});
|
||||
}
|
||||
|
||||
export async function verifyPasswordHash(
|
||||
hash: string,
|
||||
password: string,
|
||||
): Promise<boolean> {
|
||||
return await verify(hash, password);
|
||||
}
|
||||
|
||||
export async function verifyPasswordStrength(
|
||||
password: string,
|
||||
): Promise<boolean> {
|
||||
if (password.length < 8 || password.length > 255) {
|
||||
return false;
|
||||
}
|
||||
const hash = encodeHexLowerCase(sha1(new TextEncoder().encode(password)));
|
||||
const hashPrefix = hash.slice(0, 5);
|
||||
const response = await fetch(
|
||||
`https://api.pwnedpasswords.com/range/${hashPrefix}`,
|
||||
);
|
||||
const data = await response.text();
|
||||
const items = data.split('\n');
|
||||
for (const item of items) {
|
||||
const hashSuffix = item.slice(0, 35).toLowerCase();
|
||||
if (hash === hashPrefix + hashSuffix) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
83
packages/auth/src/session.ts
Normal file
83
packages/auth/src/session.ts
Normal file
@@ -0,0 +1,83 @@
|
||||
import crypto from 'node:crypto';
|
||||
import { type Session, type User, db } from '@openpanel/db';
|
||||
import { sha256 } from '@oslojs/crypto/sha2';
|
||||
import {
|
||||
encodeBase32LowerCaseNoPadding,
|
||||
encodeHexLowerCase,
|
||||
} from '@oslojs/encoding';
|
||||
|
||||
export function generateSessionToken(): string {
|
||||
const bytes = new Uint8Array(20);
|
||||
crypto.getRandomValues(bytes);
|
||||
const token = encodeBase32LowerCaseNoPadding(bytes);
|
||||
return token;
|
||||
}
|
||||
|
||||
export async function createSession(
|
||||
token: string,
|
||||
userId: string,
|
||||
): Promise<Session> {
|
||||
const sessionId = encodeHexLowerCase(sha256(new TextEncoder().encode(token)));
|
||||
const session: Session = {
|
||||
id: sessionId,
|
||||
userId,
|
||||
expiresAt: new Date(Date.now() + 1000 * 60 * 60 * 24 * 30),
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
};
|
||||
await db.session.create({
|
||||
data: session,
|
||||
});
|
||||
return session;
|
||||
}
|
||||
|
||||
export const EMPTY_SESSION: SessionValidationResult = {
|
||||
session: null,
|
||||
user: null,
|
||||
userId: null,
|
||||
};
|
||||
|
||||
export async function validateSessionToken(
|
||||
token: string | null,
|
||||
): Promise<SessionValidationResult> {
|
||||
if (!token) {
|
||||
return EMPTY_SESSION;
|
||||
}
|
||||
const sessionId = encodeHexLowerCase(sha256(new TextEncoder().encode(token)));
|
||||
const result = await db.session.findUnique({
|
||||
where: {
|
||||
id: sessionId,
|
||||
},
|
||||
include: {
|
||||
user: true,
|
||||
},
|
||||
});
|
||||
if (result === null) {
|
||||
return EMPTY_SESSION;
|
||||
}
|
||||
const { user, ...session } = result;
|
||||
if (Date.now() >= session.expiresAt.getTime()) {
|
||||
await db.session.delete({ where: { id: sessionId } });
|
||||
return EMPTY_SESSION;
|
||||
}
|
||||
if (Date.now() >= session.expiresAt.getTime() - 1000 * 60 * 60 * 24 * 15) {
|
||||
session.expiresAt = new Date(Date.now() + 1000 * 60 * 60 * 24 * 30);
|
||||
await db.session.update({
|
||||
where: {
|
||||
id: session.id,
|
||||
},
|
||||
data: {
|
||||
expiresAt: session.expiresAt,
|
||||
},
|
||||
});
|
||||
}
|
||||
return { session, user, userId: user.id };
|
||||
}
|
||||
|
||||
export async function invalidateSession(sessionId: string): Promise<void> {
|
||||
await db.session.delete({ where: { id: sessionId } });
|
||||
}
|
||||
|
||||
export type SessionValidationResult =
|
||||
| { session: Session; user: User; userId: string }
|
||||
| { session: null; user: null; userId: null };
|
||||
Reference in New Issue
Block a user