Files
plunk/apps/api/src/services/UserService.ts
T

99 lines
2.6 KiB
TypeScript

import dayjs from 'dayjs';
import {API_URI, NODE_ENV} from '../app/constants.js';
import {prisma} from '../database/prisma.js';
import {wrapRedis} from '../database/redis.js';
import {Keys} from './keys.js';
/**
* Extract base domain from URL for cookie sharing across subdomains
* e.g., "http://api.example.com" -> ".example.com"
* e.g., "http://api.localhost" -> ".localhost"
*/
function getCookieDomain(): string | undefined {
if (NODE_ENV === 'development') {
return undefined;
}
try {
const url = new URL(API_URI);
const hostname = url.hostname;
// For localhost or IP addresses, don't set a domain
if (hostname === 'localhost' || /^\d+\.\d+\.\d+\.\d+$/.test(hostname)) {
return undefined;
}
// Extract base domain (last two parts for most domains, or .localhost)
const parts = hostname.split('.');
if (parts.length >= 2) {
// For *.localhost or *.local, use the full hostname with leading dot
if (hostname.endsWith('.localhost') || hostname.endsWith('.local')) {
return '.localhost';
}
// For other domains, use the last two parts (e.g., .example.com)
return `.${parts.slice(-2).join('.')}`;
}
return undefined;
} catch {
return undefined;
}
}
export class UserService {
public static readonly COOKIE_NAME = 'next_token';
public static async id(id: string) {
return wrapRedis(Keys.User.id(id), async () => {
return prisma.user.findUnique({where: {id}});
});
}
public static async email(email: string) {
if (!email) {
return null;
}
return wrapRedis(Keys.User.email(email), async () => {
return prisma.user.findFirst({
where: {
email: {
equals: email,
mode: 'insensitive',
},
},
});
});
}
public static async projects(userId: string) {
const memberships = await prisma.user.findUnique({where: {id: userId}}).memberships({
include: {
project: true,
},
});
return memberships ? memberships.map(({project}) => project) : [];
}
/**
* Generates cookie options
* @param expires An optional expiry for this cookie (useful for a logout)
*/
public static cookieOptions(expires?: Date) {
// Check if using HTTPS from API_URI
const isHttps = NODE_ENV === 'development' ? false : API_URI.startsWith('https://');
return {
httpOnly: true,
expires: expires ?? dayjs().add(7, 'days').toDate(),
secure: isHttps,
sameSite: isHttps ? 'none' : 'lax',
path: '/',
domain: getCookieDomain(),
} as const;
}
}