Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions src/lib/server/bullmq/queues.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,11 @@ import OTEL from '$lib/otel';
class Connection {
private conn: Redis;
private connected: boolean;
constructor(isQueueConnection = false) {
constructor(isQueueConnection = false, keyPrefix?: string) {
this.conn = new Redis({
host: process.env.NODE_ENV === 'development' ? 'localhost' : process.env.VALKEY_HOST,
maxRetriesPerRequest: isQueueConnection ? undefined : null
maxRetriesPerRequest: isQueueConnection ? undefined : null,
keyPrefix
});
this.connected = false;
this.conn.on('close', () => {
Expand Down Expand Up @@ -87,7 +88,7 @@ let _authConnection: Connection | undefined = undefined;
export const QueueConnected = () => _queueConnection?.IsConnected() ?? false;

export const getAuthConnection = () => {
if (!_authConnection) _authConnection = new Connection(false);
if (!_authConnection) _authConnection = new Connection(false, 'auth');
return _authConnection.connection();
};

Expand Down
8 changes: 4 additions & 4 deletions src/routes/(unauthenticated)/api/auth/exchange/+server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,17 +24,17 @@ export async function POST({ locals, request }) {
if (body.success) {
// get cached challenge and token
const [challenge, token] = await getAuthConnection().mget(
`auth:code:${body.output.code}`,
`auth:token:${body.output.code}`
`code:${body.output.code}`,
`token:${body.output.code}`
);
if (!challenge || !token) error(400, 'Invalid or expired code');

try {
//immediately invalidate
await getAuthConnection()
.pipeline()
.del(`auth:code:${body.output.code}`)
.del(`auth:token:${body.output.code}`)
.del(`code:${body.output.code}`)
.del(`token:${body.output.code}`)
.exec();
} catch {
/* empty */
Expand Down
24 changes: 19 additions & 5 deletions src/routes/(unauthenticated)/api/auth/token/+server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,16 @@ export const HEAD: RequestHandler = async ({ locals }) => {
*
* Required URL Params:
* challenge: base64URL-encodeded, sha256 hash of a string that will be used in /api/auth/exchange
* redirect_uri: location to send the code to, allowed to start with localhost, 127.0.0.1 or org.sil.{sab,dab,rab,kab}: (for desktop apps)
* redirect_uri: location to send the code to
* - restrictions:
* - may start with: 'org.sil.{sab,dab,rab,kab}:' (for desktop apps)
* - may have hostname:
* - equal to 'localhost' or '127.0.0.1' (for dev and desktop apps)
* - ending with 'buildengine.scriptoria.io' (for buildengine auth)
*
* Optional URL Params:
* scope: minimum permissions requested
* - admin: checks if user is SuperAdmin
*/
export const GET: RequestHandler = async ({ locals, url }) => {
locals.security.requireNothing();
Expand All @@ -32,7 +41,9 @@ export const GET: RequestHandler = async ({ locals, url }) => {
if (!urlValid) {
try {
const url = new URL(redirectUri);
urlValid = ['localhost', '127.0.0.1'].includes(url.hostname);
urlValid =
['localhost', '127.0.0.1'].includes(url.hostname) ||
!!url.hostname.match(/buildengine\.scriptoria\.io$/);
} catch {
/*empty*/
}
Expand All @@ -42,6 +53,10 @@ export const GET: RequestHandler = async ({ locals, url }) => {
// params are valid, now do login prompt
locals.security.requireAuthenticated();

if (url.searchParams.get('scope') === 'admin') {
locals.security.requireSuperAdmin();
}

// create key from AUTH0_SECRET
if (!env.AUTH0_SECRET) {
error(500, 'Could not sign token');
Expand Down Expand Up @@ -72,11 +87,10 @@ export const GET: RequestHandler = async ({ locals, url }) => {
.sign(secret);

// store challenge and token in redis for up to 5 minutes with generated code as the key
// we may want to use IORedis key prefixes in the future (https://github.com/redis/ioredis?tab=readme-ov-file#transparent-key-prefixing)
await getAuthConnection()
.pipeline()
.set(`auth:code:${code}`, challenge, 'EX', 300)
.set(`auth:token:${code}`, token, 'EX', 300)
.set(`code:${code}`, challenge, 'EX', 300)
.set(`token:${code}`, token, 'EX', 300)
.exec(); // 5 minute (300 s) TTL
} catch {
error(500, 'Failed to generate authentication code');
Expand Down
Loading