Skip to content
Closed
Show file tree
Hide file tree
Changes from all 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
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ on:
env:
PUBLIC_SUPABASE_URL: ${{ vars.PUBLIC_SUPABASE_URL || 'http://dummy' }}
PUBLIC_SUPABASE_ANON_KEY: ${{ vars.PUBLIC_SUPABASE_ANON_KEY || 'anon-key' }}
PUBLIC_SUPABASE_SERVICE_KEY: ${{ vars.PUBLIC_SUPABASE_SERVICE_KEY }}
SUPABASE_SERVICE_KEY: ${{ vars.SUPABASE_SERVICE_KEY }}
PUBLIC_GOOGLE_SERVICE_EMAIL: ${{ vars.PUBLIC_GOOGLE_SERVICE_EMAIL }}
PUBLIC_GOOGLE_PRIVATE_KEY: ${{ vars.PUBLIC_GOOGLE_PRIVATE_KEY }}

Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ env:
# SUPABASE_ACCESS_TOKEN: ${{ secrets.SUPABASE_ACCESS_TOKEN }}
PUBLIC_SUPABASE_URL: ${{ vars.PUBLIC_SUPABASE_URL || 'http://dummy' }}
PUBLIC_SUPABASE_ANON_KEY: ${{ vars.PUBLIC_SUPABASE_ANON_KEY || 'anon-key' }}
PUBLIC_SUPABASE_SERVICE_KEY: ${{ vars.PUBLIC_SUPABASE_SERVICE_KEY }}
SUPABASE_SERVICE_KEY: ${{ secrets.SUPABASE_SERVICE_KEY }}
PUBLIC_GOOGLE_SERVICE_EMAIL: ${{ vars.PUBLIC_GOOGLE_SERVICE_EMAIL }}
PUBLIC_GOOGLE_PRIVATE_KEY: ${{ vars.PUBLIC_GOOGLE_PRIVATE_KEY }}

Expand Down Expand Up @@ -59,7 +59,7 @@ jobs:
CONTAINER_PORT=${{ env.CONTAINER_PORT }}
PUBLIC_SUPABASE_URL=${{ env.PUBLIC_SUPABASE_URL }}
PUBLIC_SUPABASE_ANON_KEY=${{ env.PUBLIC_SUPABASE_ANON_KEY }}
PUBLIC_SUPABASE_SERVICE_KEY=${{ env.PUBLIC_SUPABASE_SERVICE_KEY }}
SUPABASE_SERVICE_KEY=${{ env.SUPABASE_SERVICE_KEY }}
PUBLIC_GOOGLE_SERVICE_EMAIL=${{ env.PUBLIC_GOOGLE_SERVICE_EMAIL }}
PUBLIC_GOOGLE_PRIVATE_KEY=${{ env.PUBLIC_GOOGLE_PRIVATE_KEY }}
EOF
Expand Down
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,8 @@
/.svelte-kit
node_modules
.env
docs/
CLAUDE.md
.agents/
.claude/
skills-lock.json
3 changes: 3 additions & 0 deletions src/app.d.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { Session, SupabaseClient, User } from '@supabase/supabase-js';
import type { AppRole } from '$lib/server/auth';
import type { Database } from './database.types.ts'; // import generated types

declare global {
Expand All @@ -9,9 +10,11 @@ declare global {
safeGetSession: () => Promise<{ session: Session | null; user: User | null }>;
session: Session | null;
user: User | null;
userRole: AppRole | null;
}
interface PageData {
session: Session | null;
userRole: AppRole | null;
}
// interface PageState {}
// interface Platform {}
Expand Down
32 changes: 32 additions & 0 deletions src/lib/server/auth.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { type RequestEvent, error } from '@sveltejs/kit';

export type AppRole = 'applicant' | 'admin' | 'withdrawn' | 'inactive';

/**
* Requires authenticated user. Throws 401 if not logged in.
*/
export function requireAuth(event: RequestEvent) {
const { user, session } = event.locals;
if (!session || !user) {
throw error(401, 'Authentication required');
}
return { user, session };
}

/**
* Requires a specific role. Throws 401 if not authenticated, 403 if wrong role.
*/
export function requireRole(event: RequestEvent, role: AppRole) {
const { user, session } = requireAuth(event);
if (event.locals.userRole !== role) {
throw error(403, 'Insufficient permissions');
}
return { user, session };
}

/**
* Check if current user is admin. Does not throw.
*/
export function isAdmin(event: RequestEvent): boolean {
return event.locals.userRole === 'admin';
}
9 changes: 9 additions & 0 deletions src/lib/server/supabaseAdmin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { PUBLIC_SUPABASE_URL } from '$env/static/public';
import { SUPABASE_SERVICE_KEY } from '$env/static/private';
import { createClient } from '@supabase/supabase-js';

/**
* Service-role Supabase client. Bypasses RLS.
* ONLY use in server-side code for admin operations.
*/
export const supabaseAdmin = createClient(PUBLIC_SUPABASE_URL, SUPABASE_SERVICE_KEY);
Loading