Update authentication and authorization logic across multiple API endpoints
Replaces direct Supabase client instantiation with a unified authentication and authorization helper, introducing role-based access control to sensitive endpoints like escrow and payroll, and standardizing compliance event logging. Replit-Commit-Author: Agent Replit-Commit-Session-Id: 9203795e-937a-4306-b81d-b4d5c78c240e Replit-Commit-Checkpoint-Type: intermediate_checkpoint Replit-Commit-Event-Id: 5eb35c62-c5ab-4c7e-9552-8dc89efa29f3 Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/7c94b7a0-29c7-4f2e-94ef-44b2153872b7/9203795e-937a-4306-b81d-b4d5c78c240e/aPpJgbb Replit-Helium-Checkpoint-Created: true
This commit is contained in:
parent
f5939941d4
commit
4b0f5742af
9 changed files with 116 additions and 181 deletions
|
|
@ -1,28 +1,23 @@
|
|||
import type { VercelRequest, VercelResponse } from "@vercel/node";
|
||||
import { getAdminClient } from "../_supabase";
|
||||
import { authenticateRequest, requireAuth, requireRole, logComplianceEvent } from "../_auth";
|
||||
|
||||
export default async function handler(req: VercelRequest, res: VercelResponse) {
|
||||
const supabase = getAdminClient();
|
||||
const auth = await authenticateRequest(req);
|
||||
if (!requireAuth(auth, res)) return;
|
||||
|
||||
const authHeader = req.headers.authorization;
|
||||
if (!authHeader?.startsWith('Bearer ')) {
|
||||
return res.status(401).json({ error: 'Unauthorized' });
|
||||
}
|
||||
|
||||
const token = authHeader.split(' ')[1];
|
||||
const { data: { user }, error: authError } = await supabase.auth.getUser(token);
|
||||
|
||||
if (authError || !user) {
|
||||
return res.status(401).json({ error: 'Invalid token' });
|
||||
}
|
||||
const { userClient, adminClient, user } = auth;
|
||||
|
||||
if (req.method === 'GET') {
|
||||
// GET: Only clients and admins can view escrow records
|
||||
if (!requireRole(auth, ['client', 'admin'], res)) return;
|
||||
|
||||
const { contract_id } = req.query;
|
||||
|
||||
let query = supabase
|
||||
.from('nexus_escrow_ledger')
|
||||
.select('*')
|
||||
.or(`client_id.eq.${user.id},creator_id.eq.${user.id}`);
|
||||
// Clients can only see escrow records where they are the client
|
||||
// Admins can see all escrow records
|
||||
let query = user.user_type === 'admin'
|
||||
? adminClient.from('nexus_escrow_ledger').select('*')
|
||||
: userClient.from('nexus_escrow_ledger').select('*').eq('client_id', user.id);
|
||||
|
||||
if (contract_id) {
|
||||
query = query.eq('contract_id', contract_id);
|
||||
|
|
@ -38,13 +33,16 @@ export default async function handler(req: VercelRequest, res: VercelResponse) {
|
|||
}
|
||||
|
||||
if (req.method === 'POST') {
|
||||
// POST (funding escrow): Only clients and admins can fund
|
||||
if (!requireRole(auth, ['client', 'admin'], res)) return;
|
||||
|
||||
const { contract_id, amount } = req.body;
|
||||
|
||||
if (!contract_id || !amount) {
|
||||
return res.status(400).json({ error: 'contract_id and amount required' });
|
||||
}
|
||||
|
||||
const { data: contract } = await supabase
|
||||
const { data: contract } = await userClient
|
||||
.from('nexus_contracts')
|
||||
.select('id, client_id, creator_id, status')
|
||||
.eq('id', contract_id)
|
||||
|
|
@ -54,18 +52,19 @@ export default async function handler(req: VercelRequest, res: VercelResponse) {
|
|||
return res.status(404).json({ error: 'Contract not found' });
|
||||
}
|
||||
|
||||
if (contract.client_id !== user.id) {
|
||||
return res.status(403).json({ error: 'Only the client can fund escrow' });
|
||||
// Even admins must be the contract client to fund (or we could allow admin override)
|
||||
if (contract.client_id !== user.id && user.user_type !== 'admin') {
|
||||
return res.status(403).json({ error: 'Only the client or admin can fund escrow' });
|
||||
}
|
||||
|
||||
const { data: existing } = await supabase
|
||||
const { data: existing } = await userClient
|
||||
.from('nexus_escrow_ledger')
|
||||
.select('id, escrow_balance, funds_deposited')
|
||||
.eq('contract_id', contract_id)
|
||||
.single();
|
||||
|
||||
if (existing) {
|
||||
const { data, error } = await supabase
|
||||
const { data, error } = await userClient
|
||||
.from('nexus_escrow_ledger')
|
||||
.update({
|
||||
escrow_balance: existing.escrow_balance + amount,
|
||||
|
|
@ -85,7 +84,7 @@ export default async function handler(req: VercelRequest, res: VercelResponse) {
|
|||
return res.status(200).json({ data });
|
||||
}
|
||||
|
||||
const { data, error } = await supabase
|
||||
const { data, error } = await userClient
|
||||
.from('nexus_escrow_ledger')
|
||||
.insert({
|
||||
contract_id: contract_id,
|
||||
|
|
@ -103,19 +102,19 @@ export default async function handler(req: VercelRequest, res: VercelResponse) {
|
|||
return res.status(500).json({ error: error.message });
|
||||
}
|
||||
|
||||
await supabase.from('nexus_compliance_events').insert({
|
||||
await logComplianceEvent(adminClient, {
|
||||
entity_type: 'escrow',
|
||||
entity_id: data.id,
|
||||
event_type: 'escrow_funded',
|
||||
event_category: 'financial',
|
||||
actor_id: user.id,
|
||||
actor_role: 'client',
|
||||
actor_role: user.user_type === 'admin' ? 'admin' : 'client',
|
||||
realm_context: 'corp',
|
||||
description: `Escrow funded with $${amount}`,
|
||||
payload: { contract_id, amount },
|
||||
financial_amount: amount,
|
||||
legal_entity: 'for_profit'
|
||||
});
|
||||
}, req);
|
||||
|
||||
return res.status(201).json({ data });
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,35 +1,16 @@
|
|||
import type { VercelRequest, VercelResponse } from "@vercel/node";
|
||||
import { getAdminClient } from "../_supabase";
|
||||
import { authenticateRequest, requireAdmin, logComplianceEvent } from "../_auth";
|
||||
|
||||
export default async function handler(req: VercelRequest, res: VercelResponse) {
|
||||
const supabase = getAdminClient();
|
||||
const auth = await authenticateRequest(req);
|
||||
if (!requireAdmin(auth, res)) return;
|
||||
|
||||
const authHeader = req.headers.authorization;
|
||||
if (!authHeader?.startsWith('Bearer ')) {
|
||||
return res.status(401).json({ error: 'Unauthorized' });
|
||||
}
|
||||
|
||||
const token = authHeader.split(' ')[1];
|
||||
const { data: { user }, error: authError } = await supabase.auth.getUser(token);
|
||||
|
||||
if (authError || !user) {
|
||||
return res.status(401).json({ error: 'Invalid token' });
|
||||
}
|
||||
|
||||
const { data: userProfile } = await supabase
|
||||
.from('user_profiles')
|
||||
.select('user_type')
|
||||
.eq('id', user.id)
|
||||
.single();
|
||||
|
||||
if (userProfile?.user_type !== 'admin') {
|
||||
return res.status(403).json({ error: 'Admin access required' });
|
||||
}
|
||||
const { adminClient, user } = auth;
|
||||
|
||||
if (req.method === 'GET') {
|
||||
const { status, start_date, end_date, tax_year } = req.query;
|
||||
|
||||
let query = supabase
|
||||
let query = adminClient
|
||||
.from('nexus_payouts')
|
||||
.select(`
|
||||
*,
|
||||
|
|
@ -74,7 +55,7 @@ export default async function handler(req: VercelRequest, res: VercelResponse) {
|
|||
return res.status(400).json({ error: 'payout_ids array required' });
|
||||
}
|
||||
|
||||
const { data: payouts } = await supabase
|
||||
const { data: payouts } = await adminClient
|
||||
.from('nexus_payouts')
|
||||
.select('*')
|
||||
.in('id', payout_ids)
|
||||
|
|
@ -84,7 +65,7 @@ export default async function handler(req: VercelRequest, res: VercelResponse) {
|
|||
return res.status(400).json({ error: 'No pending payouts found' });
|
||||
}
|
||||
|
||||
const { data, error } = await supabase
|
||||
const { data, error } = await adminClient
|
||||
.from('nexus_payouts')
|
||||
.update({
|
||||
status: 'processing',
|
||||
|
|
@ -97,18 +78,18 @@ export default async function handler(req: VercelRequest, res: VercelResponse) {
|
|||
return res.status(500).json({ error: error.message });
|
||||
}
|
||||
|
||||
await supabase.from('nexus_compliance_events').insert({
|
||||
await logComplianceEvent(adminClient, {
|
||||
entity_type: 'payroll',
|
||||
entity_id: user.id,
|
||||
entity_id: user!.id,
|
||||
event_type: 'payroll_batch_processing',
|
||||
event_category: 'financial',
|
||||
actor_id: user.id,
|
||||
actor_id: user!.id,
|
||||
actor_role: 'admin',
|
||||
realm_context: 'corp',
|
||||
description: `Processing ${data?.length} payouts`,
|
||||
payload: { payout_ids, total_amount: data?.reduce((sum, p) => sum + Number(p.net_amount), 0) },
|
||||
legal_entity: 'for_profit'
|
||||
});
|
||||
}, req);
|
||||
|
||||
return res.status(200).json({
|
||||
data,
|
||||
|
|
@ -119,12 +100,12 @@ export default async function handler(req: VercelRequest, res: VercelResponse) {
|
|||
if (req.method === 'GET' && req.query.action === 'summary') {
|
||||
const currentYear = new Date().getFullYear();
|
||||
|
||||
const { data: yearPayouts } = await supabase
|
||||
const { data: yearPayouts } = await adminClient
|
||||
.from('nexus_payouts')
|
||||
.select('net_amount, status, tax_year')
|
||||
.eq('tax_year', currentYear);
|
||||
|
||||
const { data: azHours } = await supabase
|
||||
const { data: azHours } = await adminClient
|
||||
.from('nexus_time_logs')
|
||||
.select('az_eligible_hours')
|
||||
.eq('submission_status', 'approved')
|
||||
|
|
|
|||
|
|
@ -1,28 +1,19 @@
|
|||
import type { VercelRequest, VercelResponse } from "@vercel/node";
|
||||
import { getAdminClient } from "../_supabase";
|
||||
import { authenticateRequest, requireAuth, logComplianceEvent } from "../_auth";
|
||||
|
||||
export default async function handler(req: VercelRequest, res: VercelResponse) {
|
||||
if (req.method !== 'GET') {
|
||||
return res.status(405).json({ error: 'Method not allowed' });
|
||||
}
|
||||
|
||||
const supabase = getAdminClient();
|
||||
const auth = await authenticateRequest(req);
|
||||
if (!requireAuth(auth, res)) return;
|
||||
|
||||
const authHeader = req.headers.authorization;
|
||||
if (!authHeader?.startsWith('Bearer ')) {
|
||||
return res.status(401).json({ error: 'Unauthorized' });
|
||||
}
|
||||
|
||||
const token = authHeader.split(' ')[1];
|
||||
const { data: { user }, error: authError } = await supabase.auth.getUser(token);
|
||||
|
||||
if (authError || !user) {
|
||||
return res.status(401).json({ error: 'Invalid token' });
|
||||
}
|
||||
const { userClient, adminClient, user } = auth;
|
||||
|
||||
const { category, skills, experience, limit = 20, offset = 0 } = req.query;
|
||||
|
||||
const { data, error } = await supabase
|
||||
const { data, error } = await userClient
|
||||
.from('foundation_gig_radar')
|
||||
.select('*')
|
||||
.order('published_at', { ascending: false })
|
||||
|
|
@ -49,7 +40,7 @@ export default async function handler(req: VercelRequest, res: VercelResponse) {
|
|||
filteredData = filteredData.filter(d => d.required_experience === experience);
|
||||
}
|
||||
|
||||
await supabase.from('nexus_compliance_events').insert({
|
||||
await logComplianceEvent(adminClient, {
|
||||
entity_type: 'gig_radar',
|
||||
entity_id: user.id,
|
||||
event_type: 'gig_radar_accessed',
|
||||
|
|
@ -65,7 +56,7 @@ export default async function handler(req: VercelRequest, res: VercelResponse) {
|
|||
sensitive_data_accessed: false,
|
||||
cross_entity_access: true,
|
||||
legal_entity: 'non_profit'
|
||||
});
|
||||
}, req);
|
||||
|
||||
return res.status(200).json({
|
||||
data: filteredData,
|
||||
|
|
|
|||
|
|
@ -1,23 +1,14 @@
|
|||
import type { VercelRequest, VercelResponse } from "@vercel/node";
|
||||
import { getAdminClient } from "../_supabase";
|
||||
import { authenticateRequest, requireAuth, logComplianceEvent } from "../_auth";
|
||||
|
||||
export default async function handler(req: VercelRequest, res: VercelResponse) {
|
||||
const supabase = getAdminClient();
|
||||
const auth = await authenticateRequest(req);
|
||||
if (!requireAuth(auth, res)) return;
|
||||
|
||||
const authHeader = req.headers.authorization;
|
||||
if (!authHeader?.startsWith('Bearer ')) {
|
||||
return res.status(401).json({ error: 'Unauthorized' });
|
||||
}
|
||||
|
||||
const token = authHeader.split(' ')[1];
|
||||
const { data: { user }, error: authError } = await supabase.auth.getUser(token);
|
||||
|
||||
if (authError || !user) {
|
||||
return res.status(401).json({ error: 'Invalid token' });
|
||||
}
|
||||
const { userClient, adminClient, user } = auth;
|
||||
|
||||
if (req.method === 'GET') {
|
||||
const { data, error } = await supabase
|
||||
const { data, error } = await userClient
|
||||
.from('nexus_talent_profiles')
|
||||
.select('*')
|
||||
.eq('user_id', user.id)
|
||||
|
|
@ -33,7 +24,7 @@ export default async function handler(req: VercelRequest, res: VercelResponse) {
|
|||
if (req.method === 'POST') {
|
||||
const body = req.body;
|
||||
|
||||
const { data, error } = await supabase
|
||||
const { data, error } = await userClient
|
||||
.from('nexus_talent_profiles')
|
||||
.upsert({
|
||||
user_id: user.id,
|
||||
|
|
@ -54,7 +45,7 @@ export default async function handler(req: VercelRequest, res: VercelResponse) {
|
|||
return res.status(500).json({ error: error.message });
|
||||
}
|
||||
|
||||
await supabase.from('nexus_compliance_events').insert({
|
||||
await logComplianceEvent(adminClient, {
|
||||
entity_type: 'talent',
|
||||
entity_id: data.id,
|
||||
event_type: 'profile_updated',
|
||||
|
|
@ -64,13 +55,13 @@ export default async function handler(req: VercelRequest, res: VercelResponse) {
|
|||
realm_context: 'nexus',
|
||||
description: 'Talent profile updated',
|
||||
payload: { fields_updated: Object.keys(body) }
|
||||
});
|
||||
}, req);
|
||||
|
||||
return res.status(200).json({ data });
|
||||
}
|
||||
|
||||
if (req.method === 'GET' && req.query.action === 'compliance-summary') {
|
||||
const { data, error } = await supabase
|
||||
const { data, error } = await userClient
|
||||
.rpc('get_talent_compliance_summary', { p_user_id: user.id });
|
||||
|
||||
if (error) {
|
||||
|
|
|
|||
|
|
@ -1,30 +1,15 @@
|
|||
import type { VercelRequest, VercelResponse } from "@vercel/node";
|
||||
import { getAdminClient } from "../_supabase";
|
||||
import { authenticateRequest, requireAuth, logComplianceEvent } from "../_auth";
|
||||
|
||||
export default async function handler(req: VercelRequest, res: VercelResponse) {
|
||||
if (req.method !== 'POST') {
|
||||
return res.status(405).json({ error: 'Method not allowed' });
|
||||
}
|
||||
|
||||
const supabase = getAdminClient();
|
||||
const auth = await authenticateRequest(req);
|
||||
if (!requireAuth(auth, res)) return;
|
||||
|
||||
const authHeader = req.headers.authorization;
|
||||
if (!authHeader?.startsWith('Bearer ')) {
|
||||
return res.status(401).json({ error: 'Unauthorized' });
|
||||
}
|
||||
|
||||
const token = authHeader.split(' ')[1];
|
||||
const { data: { user }, error: authError } = await supabase.auth.getUser(token);
|
||||
|
||||
if (authError || !user) {
|
||||
return res.status(401).json({ error: 'Invalid token' });
|
||||
}
|
||||
|
||||
const { data: userProfile } = await supabase
|
||||
.from('user_profiles')
|
||||
.select('user_type')
|
||||
.eq('id', user.id)
|
||||
.single();
|
||||
const { userClient, adminClient, user } = auth;
|
||||
|
||||
const { time_log_id, decision, notes } = req.body;
|
||||
|
||||
|
|
@ -36,7 +21,7 @@ export default async function handler(req: VercelRequest, res: VercelResponse) {
|
|||
return res.status(400).json({ error: 'Invalid decision. Must be: approved, rejected, or needs_correction' });
|
||||
}
|
||||
|
||||
const { data: timeLog } = await supabase
|
||||
const { data: timeLog } = await adminClient
|
||||
.from('nexus_time_logs')
|
||||
.select('*, nexus_contracts!inner(client_id)')
|
||||
.eq('id', time_log_id)
|
||||
|
|
@ -47,7 +32,7 @@ export default async function handler(req: VercelRequest, res: VercelResponse) {
|
|||
}
|
||||
|
||||
const isClient = timeLog.nexus_contracts?.client_id === user.id;
|
||||
const isAdmin = userProfile?.user_type === 'admin';
|
||||
const isAdmin = user.user_type === 'admin';
|
||||
|
||||
if (!isClient && !isAdmin) {
|
||||
return res.status(403).json({ error: 'Only the contract client or admin can approve time logs' });
|
||||
|
|
@ -60,7 +45,7 @@ export default async function handler(req: VercelRequest, res: VercelResponse) {
|
|||
const newStatus = decision === 'approved' ? 'approved' :
|
||||
decision === 'rejected' ? 'rejected' : 'rejected';
|
||||
|
||||
const { data, error } = await supabase
|
||||
const { data, error } = await adminClient
|
||||
.from('nexus_time_logs')
|
||||
.update({
|
||||
submission_status: newStatus,
|
||||
|
|
@ -76,17 +61,17 @@ export default async function handler(req: VercelRequest, res: VercelResponse) {
|
|||
return res.status(500).json({ error: error.message });
|
||||
}
|
||||
|
||||
await supabase.from('nexus_time_log_audits').insert({
|
||||
await adminClient.from('nexus_time_log_audits').insert({
|
||||
time_log_id: time_log_id,
|
||||
reviewer_id: user.id,
|
||||
audit_type: decision === 'approved' ? 'approval' : 'rejection',
|
||||
decision: decision,
|
||||
notes: notes,
|
||||
ip_address: req.headers['x-forwarded-for']?.toString() || req.socket.remoteAddress,
|
||||
ip_address: req.headers['x-forwarded-for']?.toString() || req.socket?.remoteAddress,
|
||||
user_agent: req.headers['user-agent']
|
||||
});
|
||||
|
||||
await supabase.from('nexus_compliance_events').insert({
|
||||
await logComplianceEvent(adminClient, {
|
||||
entity_type: 'time_log',
|
||||
entity_id: time_log_id,
|
||||
event_type: `time_log_${decision}`,
|
||||
|
|
@ -96,7 +81,7 @@ export default async function handler(req: VercelRequest, res: VercelResponse) {
|
|||
realm_context: 'nexus',
|
||||
description: `Time log ${decision} by ${isAdmin ? 'admin' : 'client'}`,
|
||||
payload: { decision, notes }
|
||||
});
|
||||
}, req);
|
||||
|
||||
return res.status(200).json({ data });
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,26 +1,17 @@
|
|||
import type { VercelRequest, VercelResponse } from "@vercel/node";
|
||||
import { getAdminClient } from "../_supabase";
|
||||
import { authenticateRequest, requireAuth, logComplianceEvent } from "../_auth";
|
||||
|
||||
export default async function handler(req: VercelRequest, res: VercelResponse) {
|
||||
if (req.method !== 'POST') {
|
||||
return res.status(405).json({ error: 'Method not allowed' });
|
||||
}
|
||||
|
||||
const supabase = getAdminClient();
|
||||
const auth = await authenticateRequest(req);
|
||||
if (!requireAuth(auth, res)) return;
|
||||
|
||||
const authHeader = req.headers.authorization;
|
||||
if (!authHeader?.startsWith('Bearer ')) {
|
||||
return res.status(401).json({ error: 'Unauthorized' });
|
||||
}
|
||||
const { userClient, adminClient, user } = auth;
|
||||
|
||||
const token = authHeader.split(' ')[1];
|
||||
const { data: { user }, error: authError } = await supabase.auth.getUser(token);
|
||||
|
||||
if (authError || !user) {
|
||||
return res.status(401).json({ error: 'Invalid token' });
|
||||
}
|
||||
|
||||
const { data: talentProfile } = await supabase
|
||||
const { data: talentProfile } = await userClient
|
||||
.from('nexus_talent_profiles')
|
||||
.select('id')
|
||||
.eq('user_id', user.id)
|
||||
|
|
@ -36,7 +27,7 @@ export default async function handler(req: VercelRequest, res: VercelResponse) {
|
|||
return res.status(400).json({ error: 'time_log_ids array required' });
|
||||
}
|
||||
|
||||
const { data: logs, error: fetchError } = await supabase
|
||||
const { data: logs, error: fetchError } = await userClient
|
||||
.from('nexus_time_logs')
|
||||
.select('id, submission_status')
|
||||
.in('id', time_log_ids)
|
||||
|
|
@ -59,7 +50,7 @@ export default async function handler(req: VercelRequest, res: VercelResponse) {
|
|||
return res.status(400).json({ error: 'No valid time logs found' });
|
||||
}
|
||||
|
||||
const { data, error } = await supabase
|
||||
const { data, error } = await userClient
|
||||
.from('nexus_time_logs')
|
||||
.update({
|
||||
submission_status: 'submitted',
|
||||
|
|
@ -74,18 +65,18 @@ export default async function handler(req: VercelRequest, res: VercelResponse) {
|
|||
}
|
||||
|
||||
for (const log of data || []) {
|
||||
await supabase.from('nexus_time_log_audits').insert({
|
||||
await adminClient.from('nexus_time_log_audits').insert({
|
||||
time_log_id: log.id,
|
||||
reviewer_id: null,
|
||||
audit_type: 'review',
|
||||
decision: 'submitted',
|
||||
notes: 'Time log submitted for review',
|
||||
ip_address: req.headers['x-forwarded-for']?.toString() || req.socket.remoteAddress,
|
||||
ip_address: req.headers['x-forwarded-for']?.toString() || req.socket?.remoteAddress,
|
||||
user_agent: req.headers['user-agent']
|
||||
});
|
||||
}
|
||||
|
||||
await supabase.from('nexus_compliance_events').insert({
|
||||
await logComplianceEvent(adminClient, {
|
||||
entity_type: 'time_log',
|
||||
entity_id: talentProfile.id,
|
||||
event_type: 'batch_submitted',
|
||||
|
|
@ -95,7 +86,7 @@ export default async function handler(req: VercelRequest, res: VercelResponse) {
|
|||
realm_context: 'nexus',
|
||||
description: `Submitted ${data?.length} time logs for review`,
|
||||
payload: { time_log_ids: validIds }
|
||||
});
|
||||
}, req);
|
||||
|
||||
return res.status(200).json({
|
||||
data,
|
||||
|
|
|
|||
|
|
@ -1,22 +1,13 @@
|
|||
import type { VercelRequest, VercelResponse } from "@vercel/node";
|
||||
import { getAdminClient } from "../_supabase";
|
||||
import { authenticateRequest, requireAuth } from "../_auth";
|
||||
|
||||
export default async function handler(req: VercelRequest, res: VercelResponse) {
|
||||
const supabase = getAdminClient();
|
||||
const auth = await authenticateRequest(req);
|
||||
if (!requireAuth(auth, res)) return;
|
||||
|
||||
const authHeader = req.headers.authorization;
|
||||
if (!authHeader?.startsWith('Bearer ')) {
|
||||
return res.status(401).json({ error: 'Unauthorized' });
|
||||
}
|
||||
const { userClient, user } = auth;
|
||||
|
||||
const token = authHeader.split(' ')[1];
|
||||
const { data: { user }, error: authError } = await supabase.auth.getUser(token);
|
||||
|
||||
if (authError || !user) {
|
||||
return res.status(401).json({ error: 'Invalid token' });
|
||||
}
|
||||
|
||||
const { data: talentProfile } = await supabase
|
||||
const { data: talentProfile } = await userClient
|
||||
.from('nexus_talent_profiles')
|
||||
.select('id, az_eligible')
|
||||
.eq('user_id', user.id)
|
||||
|
|
@ -29,7 +20,7 @@ export default async function handler(req: VercelRequest, res: VercelResponse) {
|
|||
if (req.method === 'GET') {
|
||||
const { contract_id, start_date, end_date, status } = req.query;
|
||||
|
||||
let query = supabase
|
||||
let query = userClient
|
||||
.from('nexus_time_logs')
|
||||
.select('*')
|
||||
.eq('talent_profile_id', talentProfile.id)
|
||||
|
|
@ -56,7 +47,7 @@ export default async function handler(req: VercelRequest, res: VercelResponse) {
|
|||
? body.hours_worked
|
||||
: 0;
|
||||
|
||||
const { data, error } = await supabase
|
||||
const { data, error } = await userClient
|
||||
.from('nexus_time_logs')
|
||||
.insert({
|
||||
talent_profile_id: talentProfile.id,
|
||||
|
|
@ -95,7 +86,7 @@ export default async function handler(req: VercelRequest, res: VercelResponse) {
|
|||
return res.status(400).json({ error: 'Time log ID required' });
|
||||
}
|
||||
|
||||
const { data: existingLog } = await supabase
|
||||
const { data: existingLog } = await userClient
|
||||
.from('nexus_time_logs')
|
||||
.select('*')
|
||||
.eq('id', id)
|
||||
|
|
@ -114,7 +105,7 @@ export default async function handler(req: VercelRequest, res: VercelResponse) {
|
|||
? (body.hours_worked || existingLog.hours_worked)
|
||||
: 0;
|
||||
|
||||
const { data, error } = await supabase
|
||||
const { data, error } = await userClient
|
||||
.from('nexus_time_logs')
|
||||
.update({
|
||||
...body,
|
||||
|
|
@ -139,7 +130,7 @@ export default async function handler(req: VercelRequest, res: VercelResponse) {
|
|||
return res.status(400).json({ error: 'Time log ID required' });
|
||||
}
|
||||
|
||||
const { data: existingLog } = await supabase
|
||||
const { data: existingLog } = await userClient
|
||||
.from('nexus_time_logs')
|
||||
.select('submission_status')
|
||||
.eq('id', id)
|
||||
|
|
@ -154,7 +145,7 @@ export default async function handler(req: VercelRequest, res: VercelResponse) {
|
|||
return res.status(400).json({ error: 'Can only delete draft time logs' });
|
||||
}
|
||||
|
||||
const { error } = await supabase
|
||||
const { error } = await userClient
|
||||
.from('nexus_time_logs')
|
||||
.delete()
|
||||
.eq('id', id);
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import type { VercelRequest, VercelResponse } from "@vercel/node";
|
||||
import { getAdminClient } from "../_supabase";
|
||||
import { getAdminClient, getUserClient } from "../_auth";
|
||||
|
||||
const STUDIO_API_KEY = process.env.STUDIO_API_KEY;
|
||||
|
||||
|
|
@ -8,23 +8,25 @@ export default async function handler(req: VercelRequest, res: VercelResponse) {
|
|||
return res.status(405).json({ error: 'Method not allowed' });
|
||||
}
|
||||
|
||||
const supabase = getAdminClient();
|
||||
|
||||
const apiKey = req.headers['x-studio-api-key'] || req.headers['authorization']?.replace('Bearer ', '');
|
||||
|
||||
const apiKey = req.headers['x-studio-api-key'];
|
||||
const authHeader = req.headers.authorization;
|
||||
|
||||
let userId: string | null = null;
|
||||
let isServiceAuth = false;
|
||||
let supabase: any;
|
||||
|
||||
if (apiKey === STUDIO_API_KEY && STUDIO_API_KEY) {
|
||||
isServiceAuth = true;
|
||||
supabase = getAdminClient();
|
||||
} else if (authHeader?.startsWith('Bearer ')) {
|
||||
const token = authHeader.split(' ')[1];
|
||||
const { data: { user }, error } = await supabase.auth.getUser(token);
|
||||
const adminClient = getAdminClient();
|
||||
const { data: { user }, error } = await adminClient.auth.getUser(token);
|
||||
if (error || !user) {
|
||||
return res.status(401).json({ error: 'Invalid token' });
|
||||
}
|
||||
userId = user.id;
|
||||
supabase = getUserClient(token);
|
||||
} else {
|
||||
return res.status(401).json({ error: 'Unauthorized' });
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,30 +1,34 @@
|
|||
import type { VercelRequest, VercelResponse } from "@vercel/node";
|
||||
import { getAdminClient } from "../_supabase";
|
||||
import { getAdminClient, getUserClient, logComplianceEvent } from "../_auth";
|
||||
|
||||
const STUDIO_API_KEY = process.env.STUDIO_API_KEY;
|
||||
|
||||
export default async function handler(req: VercelRequest, res: VercelResponse) {
|
||||
const supabase = getAdminClient();
|
||||
|
||||
const apiKey = req.headers['x-studio-api-key'] || req.headers['authorization']?.replace('Bearer ', '');
|
||||
|
||||
const apiKey = req.headers['x-studio-api-key'];
|
||||
const authHeader = req.headers.authorization;
|
||||
|
||||
let userId: string | null = null;
|
||||
let isServiceAuth = false;
|
||||
let supabase: any;
|
||||
|
||||
if (apiKey === STUDIO_API_KEY && STUDIO_API_KEY) {
|
||||
isServiceAuth = true;
|
||||
supabase = getAdminClient();
|
||||
} else if (authHeader?.startsWith('Bearer ')) {
|
||||
const token = authHeader.split(' ')[1];
|
||||
const { data: { user }, error } = await supabase.auth.getUser(token);
|
||||
const adminClient = getAdminClient();
|
||||
const { data: { user }, error } = await adminClient.auth.getUser(token);
|
||||
if (error || !user) {
|
||||
return res.status(401).json({ error: 'Invalid token' });
|
||||
}
|
||||
userId = user.id;
|
||||
supabase = getUserClient(token);
|
||||
} else {
|
||||
return res.status(401).json({ error: 'Unauthorized - requires Bearer token or X-Studio-API-Key' });
|
||||
}
|
||||
|
||||
const adminClient = getAdminClient();
|
||||
|
||||
if (req.method === 'POST') {
|
||||
const body = req.body;
|
||||
|
||||
|
|
@ -34,7 +38,7 @@ export default async function handler(req: VercelRequest, res: VercelResponse) {
|
|||
|
||||
const targetUserId = body.user_id || userId;
|
||||
|
||||
const { data: talentProfile } = await supabase
|
||||
const { data: talentProfile } = await adminClient
|
||||
.from('nexus_talent_profiles')
|
||||
.select('id, az_eligible')
|
||||
.eq('user_id', targetUserId)
|
||||
|
|
@ -48,7 +52,7 @@ export default async function handler(req: VercelRequest, res: VercelResponse) {
|
|||
? body.hours_worked
|
||||
: 0;
|
||||
|
||||
const { data, error } = await supabase
|
||||
const { data, error } = await adminClient
|
||||
.from('nexus_time_logs')
|
||||
.insert({
|
||||
talent_profile_id: talentProfile.id,
|
||||
|
|
@ -77,12 +81,12 @@ export default async function handler(req: VercelRequest, res: VercelResponse) {
|
|||
return res.status(500).json({ error: error.message });
|
||||
}
|
||||
|
||||
await supabase.from('nexus_compliance_events').insert({
|
||||
await logComplianceEvent(adminClient, {
|
||||
entity_type: 'time_log',
|
||||
entity_id: data.id,
|
||||
event_type: 'studio_time_log_created',
|
||||
event_category: 'compliance',
|
||||
actor_id: isServiceAuth ? null : userId,
|
||||
actor_id: isServiceAuth ? undefined : userId || undefined,
|
||||
actor_role: isServiceAuth ? 'api' : 'talent',
|
||||
realm_context: 'studio',
|
||||
description: 'Time log submitted via Studio API',
|
||||
|
|
@ -91,7 +95,7 @@ export default async function handler(req: VercelRequest, res: VercelResponse) {
|
|||
location_verified: data.location_verified,
|
||||
az_eligible_hours: azEligibleHours
|
||||
}
|
||||
});
|
||||
}, req);
|
||||
|
||||
return res.status(201).json({ data });
|
||||
}
|
||||
|
|
@ -114,7 +118,7 @@ export default async function handler(req: VercelRequest, res: VercelResponse) {
|
|||
`);
|
||||
|
||||
if (targetUserId) {
|
||||
const { data: talentProfile } = await supabase
|
||||
const { data: talentProfile } = await adminClient
|
||||
.from('nexus_talent_profiles')
|
||||
.select('id')
|
||||
.eq('user_id', targetUserId)
|
||||
|
|
|
|||
Loading…
Reference in a new issue