41 create tenant twilio config (#62)
* add tenant config table * add encryption/decryption + env vars * generate secret and validate iv position is number * expect errors * remove TWILIO env vars * settings page impl * update schema definitions after Mostaphas Tenant impl * load user env * just return empty config * add Settings menu item * check if settings are present and provide warning if not * correct form item names * use correct locals value * ree * give twilio its own table * lock prisma version * event url is the correct param * load twilio config from db * commit migration * use test script not bun command
This commit is contained in:
parent
8006d523c7
commit
8270c53509
24 changed files with 515 additions and 57 deletions
20
src/lib/server/crypto/encryption.test.ts
Normal file
20
src/lib/server/crypto/encryption.test.ts
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
import { describe, expect, it, vi } from 'vitest';
|
||||
import { decrypt, encrypt } from './encryption';
|
||||
|
||||
describe('Encryption', () => {
|
||||
it('should encrypt and decrypt data', () => {
|
||||
const data = 'aye its ya boi!';
|
||||
|
||||
vi.mock('$env/static/private', () => ({
|
||||
SECRETS_PASSWORD: 'aac7405eb3384e68c285fc252dbf68b2',
|
||||
SECRETS_SALT: 'c4aeaf8bda72ea45e8c23269ca849013',
|
||||
SECRETS_IV_POSITION: 9,
|
||||
}));
|
||||
|
||||
const encrypted = encrypt(data);
|
||||
console.log(encrypted);
|
||||
const decrypted = decrypt(encrypted);
|
||||
|
||||
expect(decrypted).toEqual(data);
|
||||
});
|
||||
});
|
||||
42
src/lib/server/crypto/encryption.ts
Normal file
42
src/lib/server/crypto/encryption.ts
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
import { SECRETS_PASSWORD, SECRETS_IV_POSITION, SECRETS_SALT } from '$env/static/private';
|
||||
import { createCipheriv, createDecipheriv, randomBytes, scryptSync } from 'node:crypto';
|
||||
|
||||
const algorithm = 'aes-256-gcm';
|
||||
const password = SECRETS_PASSWORD;
|
||||
const salt = SECRETS_SALT;
|
||||
const iv_position = Number(SECRETS_IV_POSITION);
|
||||
|
||||
function construct(encrypted: string, tag: Buffer, iv: { value: Buffer; position: number }) {
|
||||
return `${encrypted.slice(0, iv.position)}${iv.value.toString('hex')}${encrypted.slice(iv.position)}${tag.toString('hex')}`;
|
||||
}
|
||||
|
||||
function deconstruct(encrypted: string, position: number): [Buffer, string, Buffer] {
|
||||
const iv = encrypted.slice(position, position + 16);
|
||||
const text = `${encrypted.slice(0, position)}${encrypted.slice(position + 16, encrypted.length - 32)}`;
|
||||
const authTag = encrypted.slice(encrypted.length - 32);
|
||||
return [Buffer.from(iv, 'hex'), text, Buffer.from(authTag, 'hex')];
|
||||
}
|
||||
|
||||
export function encrypt(value: string): string {
|
||||
const key = scryptSync(password, salt, 32);
|
||||
const iv = randomBytes(8);
|
||||
|
||||
// @ts-expect-error Bun typing mismatch, but it still works!
|
||||
const cipher = createCipheriv(algorithm, key, iv);
|
||||
const encrypted = cipher.update(value, 'utf-8', 'hex') + cipher.final('hex');
|
||||
const authTag = cipher.getAuthTag();
|
||||
|
||||
return construct(encrypted, authTag, { value: iv, position: iv_position });
|
||||
}
|
||||
|
||||
export function decrypt(value: string): string {
|
||||
const key = scryptSync(password, salt, 32);
|
||||
const [iv, text, authTag] = deconstruct(value, iv_position);
|
||||
|
||||
// @ts-expect-error Bun typing mismatch, but it still works!
|
||||
const decipher = createDecipheriv(algorithm, key, iv);
|
||||
// @ts-expect-error Bun typing mismatch, but it still works!
|
||||
decipher.setAuthTag(authTag);
|
||||
|
||||
return decipher.update(text, 'hex', 'utf-8') + decipher.final('utf-8');
|
||||
}
|
||||
1
src/lib/server/crypto/index.ts
Normal file
1
src/lib/server/crypto/index.ts
Normal file
|
|
@ -0,0 +1 @@
|
|||
export * from './encryption';
|
||||
1
src/lib/server/middleware/index.ts
Normal file
1
src/lib/server/middleware/index.ts
Normal file
|
|
@ -0,0 +1 @@
|
|||
export * from './server';
|
||||
1
src/lib/server/middleware/server/index.ts
Normal file
1
src/lib/server/middleware/server/index.ts
Normal file
|
|
@ -0,0 +1 @@
|
|||
export * from './loadUserEnv';
|
||||
40
src/lib/server/middleware/server/loadUserEnv.ts
Normal file
40
src/lib/server/middleware/server/loadUserEnv.ts
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
import { logger } from '$lib/server/logger';
|
||||
import { prisma } from '$lib/server/prisma';
|
||||
import type { Handle } from '@sveltejs/kit';
|
||||
|
||||
const publicRoutes = ['/login'];
|
||||
|
||||
export function loadUserEnv(): Handle {
|
||||
return async ({ event, resolve }) => {
|
||||
if (event.url !== null && publicRoutes.includes(event.url.pathname)) {
|
||||
return resolve(event);
|
||||
}
|
||||
try {
|
||||
const tenant = await prisma.tenant.findUniqueOrThrow({
|
||||
where: {
|
||||
clerkOrganizationId: event.locals.auth.orgId ?? undefined,
|
||||
},
|
||||
});
|
||||
const user = await prisma.user.findUniqueOrThrow({
|
||||
where: {
|
||||
clerkId_tenantId: { clerkId: event.locals.auth.userId!, tenantId: tenant.id },
|
||||
},
|
||||
});
|
||||
|
||||
event.locals.tenant = tenant;
|
||||
event.locals.user = user;
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
logger.error(error);
|
||||
}
|
||||
return new Response(null, {
|
||||
status: 307,
|
||||
headers: {
|
||||
location: '/login',
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
return resolve(event);
|
||||
};
|
||||
}
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
import { TWILIO_ACCOUNT_SID, TWILIO_AUTH_TOKEN } from '$env/static/private';
|
||||
import twilio from 'twilio';
|
||||
|
||||
export const TwilioClient = twilio(TWILIO_ACCOUNT_SID, TWILIO_AUTH_TOKEN);
|
||||
|
|
@ -1 +0,0 @@
|
|||
export * from './client';
|
||||
Loading…
Add table
Add a link
Reference in a new issue