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:
Carl-Gerhard Lindesvärd
2024-12-18 21:30:39 +01:00
committed by Carl-Gerhard Lindesvärd
parent f28802b1c2
commit d31d9924a5
151 changed files with 18484 additions and 12853 deletions

View File

@@ -0,0 +1,119 @@
import {
chQuery,
db,
getClientByIdCached,
getProjectByIdCached,
} from '../index';
import { stripTrailingSlash } from '@openpanel/common';
const pickBestDomain = (domains: string[]): string | null => {
// Filter out invalid domains
const validDomains = domains.filter(
(domain) =>
domain &&
!domain.includes('*') &&
!domain.includes('localhost') &&
!domain.includes('127.0.0.1'),
);
if (validDomains.length === 0) return null;
// Score each domain
const scoredDomains = validDomains.map((domain) => {
let score = 0;
// Prefer https (highest priority)
if (domain.startsWith('https://')) score += 100;
// Penalize domains from common providers like vercel, netlify, etc.
if (
domain.includes('vercel.app') ||
domain.includes('netlify.app') ||
domain.includes('herokuapp.com') ||
domain.includes('github.io') ||
domain.includes('gitlab.io') ||
domain.includes('surge.sh') ||
domain.includes('cloudfront.net') ||
domain.includes('firebaseapp.com') ||
domain.includes('azurestaticapps.net') ||
domain.includes('pages.dev') ||
domain.includes('ngrok-free.app') ||
domain.includes('ngrok.app')
) {
score -= 50;
}
// Penalize subdomains
const domainParts = domain
.replace('https://', '')
.replace('http://', '')
.split('.');
if (domainParts.length <= 2) score += 50;
// Tiebreaker: prefer shorter domains
score -= domain.length;
return { domain, score };
});
// Sort by score (highest first) and return the best domain
const bestDomain = scoredDomains.sort((a, b) => b.score - a.score)[0];
return bestDomain?.domain || null;
};
export const up = async () => {
const projects = await db.project.findMany({
include: {
clients: true,
},
});
const matches = [];
for (const project of projects) {
if (project.cors.length > 0 || project.domain) {
continue;
}
if (project.clients.length === 0) {
continue;
}
const cors = [];
let crossDomain = false;
for (const client of project.clients) {
if (client.crossDomain) {
crossDomain = true;
}
cors.push(
...(client.cors?.split(',') ?? []).map((c) =>
stripTrailingSlash(c.trim()),
),
);
await getClientByIdCached.clear(client.id);
}
let domain = pickBestDomain(cors);
if (!domain) {
const res = await chQuery<{ origin: string }>(
`SELECT origin FROM events_distributed WHERE project_id = '${project.id}' and origin != ''`,
);
if (res.length) {
domain = pickBestDomain(res.map((r) => r.origin));
matches.push(domain);
}
}
await db.project.update({
where: { id: project.id },
data: {
cors,
crossDomain,
domain,
},
});
await getProjectByIdCached.clear(project.id);
}
};

View File

@@ -0,0 +1,88 @@
import fs from 'node:fs/promises';
import path from 'node:path';
import { db } from '../index';
import { printBoxMessage } from './helpers';
const simpleCsvParser = (csv: string): Record<string, unknown>[] => {
const rows = csv.split('\n');
const headers = rows[0]!.split(',');
return rows.slice(1).map((row) =>
row.split(',').reduce(
(acc, curr, index) => {
acc[headers[index]!] = curr;
return acc;
},
{} as Record<string, unknown>,
),
);
};
async function checkFileExists(filePath: string): Promise<boolean> {
try {
await fs.access(filePath);
return true; // File exists
} catch (error) {
return false; // File does not exist
}
}
export async function up() {
const accountCount = await db.account.count();
const userCount = await db.user.count();
if (accountCount > 0) {
printBoxMessage('⏭️ Skipping Migration ⏭️', ['Accounts already migrated']);
return;
}
if (userCount === 0) {
printBoxMessage('⏭️ Skipping Migration ⏭️', [
'No users found, skipping migration',
]);
return;
}
const dumppath = path.join(__dirname, 'users-dump.csv');
// check if file exists
if (!(await checkFileExists(dumppath))) {
printBoxMessage('⚠️ Missing Required File ⚠️', [
`File not found: ${dumppath}`,
'This file is required to run this migration',
'',
'You can export it from:',
'Clerk > Configure > Settings > Export all users',
]);
throw new Error('Required users dump file not found');
}
const csv = await fs.readFile(path.join(__dirname, 'users-dump.csv'), 'utf8');
const data = simpleCsvParser(csv);
for (const row of data) {
const email =
row.primary_email_address ||
row.verified_email_addresses ||
row.unverified_email_addresses;
if (!email) {
continue;
}
const user = await db.user.findUnique({
where: {
email: String(email),
},
});
if (!user) {
continue;
}
await db.account.create({
data: {
userId: user.id,
provider: row.password_digest ? 'email' : 'oauth',
providerId: null,
password: row.password_digest ? String(row.password_digest) : null,
},
});
}
}

View File

@@ -0,0 +1,13 @@
export function printBoxMessage(title: string, lines: (string | unknown)[]) {
console.log('┌──┐');
console.log('│');
if (title) {
console.log(`${title}`);
console.log('│');
}
lines.forEach((line) => {
console.log(`${line}`);
});
console.log('│');
console.log('└──┘');
}

View File

@@ -0,0 +1,62 @@
import fs from 'node:fs';
import path from 'node:path';
import { ch, db } from '../index';
import { printBoxMessage } from './helpers';
async function migrate() {
const args = process.argv.slice(2);
const migration = args[0];
const migrationsDir = path.join(__dirname, '..', 'code-migrations');
const migrations = fs.readdirSync(migrationsDir).filter((file) => {
const version = file.split('-')[0];
return (
!Number.isNaN(Number.parseInt(version ?? '')) && file.endsWith('.ts')
);
});
if (migration) {
await runMigration(migrationsDir, migration);
} else {
const finishedMigrations = await db.codeMigration.findMany();
for (const file of migrations) {
if (finishedMigrations.some((migration) => migration.name === file)) {
printBoxMessage('⏭️ Skipping Migration ⏭️', [`${file}`]);
continue;
}
await runMigration(migrationsDir, file);
}
}
console.log('Migrations finished');
process.exit(0);
}
async function runMigration(migrationsDir: string, file: string) {
printBoxMessage('⚡️ Running Migration ⚡️ ', [`${file}`]);
try {
const migration = await import(path.join(migrationsDir, file));
await migration.up();
await db.codeMigration.upsert({
where: {
name: file,
},
update: {
name: file,
},
create: {
name: file,
},
});
} catch (error) {
printBoxMessage('❌ Migration Failed ❌', [
`Error running migration ${file}:`,
error,
]);
process.exit(1);
}
}
migrate();