/**
* MP Verification Server Actions
* Handles MP verification requests and email verification
*/
'use server';
import { createServerClient, createAdminClient } from '@/lib/supabase-server';
import { auth } from '@/auth';
import { sendDigestEmail } from '@/lib/email/resend-client';
import { render } from '@react-email/components';
import { MPVerificationEmail } from '@/lib/email/mp-verification-email';
import type { ApiResponse } from '@/types/forum';
import crypto from 'crypto';
interface MPVerificationRequest {
id: string;
user_id: string;
email: string;
parl_mp_id: number | null;
mp_name: string;
mp_riding: string;
mp_party: string | null;
proof_type: string | null;
proof_notes: string | null;
attachments: any | null;
status: 'pending' | 'approved' | 'rejected' | 'cancelled';
reviewed_by: string | null;
reviewed_at: string | null;
admin_notes: string | null;
created_at: string;
updated_at: string;
}
interface MPVerificationStatus {
is_verified: boolean;
mp_name: string | null;
mp_riding: string | null;
mp_party: string | null;
verified_at: string | null;
verification_method: string | null;
has_pending_request: boolean;
}
/**
* Request MP verification
* If @parl.gc.ca email → sends verification email
* Otherwise → creates manual verification request
*/
export async function requestMPVerification(data: {
mpId: number; // parl_mp_id from Neo4j
mpName: string;
mpRiding: string;
mpParty: string;
email: string;
proofType?: string;
proofNotes?: string;
}): Promise<ApiResponse<{ type: 'email' | 'manual'; token?: string }>> {
try {
const supabase = await createServerClient();
const session = await auth();
if (!session?.user?.id) {
return { success: false, error: 'Not authenticated' };
}
const { mpId, mpName, mpRiding, mpParty, email, proofType, proofNotes } = data;
// Check if user already verified
const { data: profile } = await supabase
.from('user_profiles')
.select('is_verified_mp')
.eq('id', session.user.id)
.single();
if (profile?.is_verified_mp) {
return { success: false, error: 'You are already verified as an MP' };
}
// Check if user has pending request
const { data: existingRequest } = await supabase
.from('mp_verification_requests')
.select('id')
.eq('user_id', session.user.id)
.eq('status', 'pending')
.maybeSingle();
if (existingRequest) {
return { success: false, error: 'You already have a pending verification request' };
}
// Check if email is @parl.gc.ca → automatic email verification
const isParlEmail = email.toLowerCase().endsWith('@parl.gc.ca');
if (!isParlEmail) {
return {
success: false,
error: 'MP verification requires a @parl.gc.ca email address. Please sign in with your parliamentary email to verify your MP status.',
};
}
if (isParlEmail) {
// Generate verification token
const token = crypto.randomBytes(32).toString('hex');
const expiresAt = new Date();
expiresAt.setHours(expiresAt.getHours() + 24); // 24-hour expiry
// Store token using admin client to bypass RLS
const adminClient = createAdminClient();
const { error: tokenError } = await adminClient
.from('mp_email_verification_tokens')
.insert({
user_id: session.user.id,
email,
token,
parl_mp_id: mpId,
mp_name: mpName,
mp_riding: mpRiding,
mp_party: mpParty,
expires_at: expiresAt.toISOString(),
});
if (tokenError) throw tokenError;
// Send verification email
const verificationUrl = `${process.env.NEXT_PUBLIC_APP_URL || 'https://canadagpt.ca'}/api/mp-verification/verify?token=${token}`;
const emailHtml = await render(
MPVerificationEmail({
mpName,
mpRiding,
verificationUrl,
})
);
const emailText = `
CanadaGPT MP Verification
Hello ${mpName},
Thank you for verifying your identity as a Member of Parliament on CanadaGPT.
To complete your verification, please click the link below:
${verificationUrl}
This link will expire in 24 hours.
If you did not request this verification, please ignore this email.
---
CanadaGPT Team
`;
const sendResult = await sendDigestEmail({
to: email,
subject: 'Verify your MP status on CanadaGPT',
html: emailHtml,
text: emailText,
});
if (!sendResult.success) {
throw new Error(sendResult.error || 'Failed to send verification email');
}
return {
success: true,
data: { type: 'email', token },
};
} else {
// Manual verification request
const { error: requestError } = await supabase
.from('mp_verification_requests')
.insert({
user_id: session.user.id,
email,
parl_mp_id: mpId,
mp_name: mpName,
mp_riding: mpRiding,
mp_party: mpParty,
proof_type: proofType || 'other',
proof_notes: proofNotes,
status: 'pending',
});
if (requestError) throw requestError;
return {
success: true,
data: { type: 'manual' },
};
}
} catch (error) {
console.error('Error requesting MP verification:', error);
return {
success: false,
error: error instanceof Error ? error.message : 'Failed to request verification',
};
}
}
/**
* Verify MP email token
* Called when user clicks verification link
*/
export async function verifyMPEmail(token: string): Promise<ApiResponse<{ mpName: string }>> {
try {
// Use admin client to bypass RLS for token verification
const adminClient = createAdminClient();
// Get token details
const { data: tokenData, error: tokenError } = await adminClient
.from('mp_email_verification_tokens')
.select('*')
.eq('token', token)
.is('used_at', null)
.single();
if (tokenError || !tokenData) {
return { success: false, error: 'Invalid or expired verification token' };
}
// Check if token expired
if (new Date(tokenData.expires_at) < new Date()) {
return { success: false, error: 'Verification token has expired' };
}
// Update user profile
const { error: profileError } = await adminClient
.from('user_profiles')
.update({
is_verified_mp: true,
verified_at: new Date().toISOString(),
verification_method: 'email',
parl_mp_id: tokenData.parl_mp_id,
mp_name: tokenData.mp_name,
mp_riding: tokenData.mp_riding,
mp_party: tokenData.mp_party,
})
.eq('id', tokenData.user_id);
if (profileError) throw profileError;
// Mark token as used
await adminClient
.from('mp_email_verification_tokens')
.update({ used_at: new Date().toISOString() })
.eq('id', tokenData.id);
return {
success: true,
data: { mpName: tokenData.mp_name },
};
} catch (error) {
console.error('Error verifying MP email:', error);
return {
success: false,
error: error instanceof Error ? error.message : 'Verification failed',
};
}
}
/**
* Get current user's MP verification status
*/
export async function getMPVerificationStatus(): Promise<ApiResponse<MPVerificationStatus>> {
try {
const supabase = await createServerClient();
const session = await auth();
if (!session?.user?.id) {
return { success: false, error: 'Not authenticated' };
}
const { data, error } = await supabase.rpc('get_mp_verification_status', {
p_user_id: session.user.id,
});
if (error) throw error;
return {
success: true,
data: data[0] || {
is_verified: false,
mp_name: null,
mp_riding: null,
mp_party: null,
verified_at: null,
verification_method: null,
has_pending_request: false,
},
};
} catch (error) {
console.error('Error getting MP verification status:', error);
return {
success: false,
error: error instanceof Error ? error.message : 'Failed to get status',
};
}
}
/**
* Get pending MP verification requests (admin only)
*/
export async function getPendingMPVerifications(): Promise<
ApiResponse<MPVerificationRequest[]>
> {
try {
const supabase = await createServerClient();
const session = await auth();
if (!session?.user?.id) {
return { success: false, error: 'Not authenticated' };
}
// Check if user is admin
const { data: profile } = await supabase
.from('user_profiles')
.select('is_admin')
.eq('id', session.user.id)
.single();
if (!profile?.is_admin) {
return { success: false, error: 'Unauthorized: Admin access required' };
}
// Get pending requests
const { data, error } = await supabase
.from('mp_verification_requests')
.select('*')
.eq('status', 'pending')
.order('created_at', { ascending: false });
if (error) throw error;
return { success: true, data: data || [] };
} catch (error) {
console.error('Error getting pending verifications:', error);
return {
success: false,
error: error instanceof Error ? error.message : 'Failed to get requests',
};
}
}
/**
* Approve MP verification request (admin only)
*/
export async function approveMPVerification(
requestId: string,
adminNotes?: string
): Promise<ApiResponse<void>> {
try {
const supabase = await createServerClient();
const session = await auth();
if (!session?.user?.id) {
return { success: false, error: 'Not authenticated' };
}
const { error } = await supabase.rpc('approve_mp_verification_request', {
p_request_id: requestId,
p_admin_id: session.user.id,
p_admin_notes: adminNotes || null,
});
if (error) throw error;
return { success: true };
} catch (error) {
console.error('Error approving MP verification:', error);
return {
success: false,
error: error instanceof Error ? error.message : 'Failed to approve request',
};
}
}
/**
* Reject MP verification request (admin only)
*/
export async function rejectMPVerification(
requestId: string,
adminNotes: string
): Promise<ApiResponse<void>> {
try {
const supabase = await createServerClient();
const session = await auth();
if (!session?.user?.id) {
return { success: false, error: 'Not authenticated' };
}
const { error } = await supabase.rpc('reject_mp_verification_request', {
p_request_id: requestId,
p_admin_id: session.user.id,
p_admin_notes: adminNotes,
});
if (error) throw error;
return { success: true };
} catch (error) {
console.error('Error rejecting MP verification:', error);
return {
success: false,
error: error instanceof Error ? error.message : 'Failed to reject request',
};
}
}
/**
* Revoke MP verification (admin only, for fraud cases)
*/
export async function revokeMPVerification(
userId: string,
reason: string
): Promise<ApiResponse<void>> {
try {
const supabase = await createServerClient();
const session = await auth();
if (!session?.user?.id) {
return { success: false, error: 'Not authenticated' };
}
const { error } = await supabase.rpc('revoke_mp_verification', {
p_user_id: userId,
p_admin_id: session.user.id,
p_reason: reason,
});
if (error) throw error;
return { success: true };
} catch (error) {
console.error('Error revoking MP verification:', error);
return {
success: false,
error: error instanceof Error ? error.message : 'Failed to revoke verification',
};
}
}
/**
* Cancel own pending verification request
*/
export async function cancelMPVerificationRequest(): Promise<ApiResponse<void>> {
try {
const supabase = await createServerClient();
const session = await auth();
if (!session?.user?.id) {
return { success: false, error: 'Not authenticated' };
}
const { error } = await supabase
.from('mp_verification_requests')
.update({ status: 'cancelled', updated_at: new Date().toISOString() })
.eq('user_id', session.user.id)
.eq('status', 'pending');
if (error) throw error;
return { success: true };
} catch (error) {
console.error('Error cancelling verification request:', error);
return {
success: false,
error: error instanceof Error ? error.message : 'Failed to cancel request',
};
}
}