feat: Integrate auth.sessions with RPC functions

This commit is contained in:
gpt-engineer-app[bot]
2025-10-14 18:26:22 +00:00
parent 098ca9f3b0
commit b129f88f3a
3 changed files with 121 additions and 80 deletions

View File

@@ -23,20 +23,15 @@ import type { UserIdentity, OAuthProvider } from '@/types/identity';
import { toast as sonnerToast } from '@/components/ui/sonner';
import { supabase } from '@/integrations/supabase/client';
interface DeviceInfo {
browser?: string;
userAgent?: string;
os?: string;
device?: string;
}
interface UserSession {
interface AuthSession {
id: string;
device_info: DeviceInfo | null;
last_activity: string;
created_at: string;
expires_at: string;
session_token: string;
updated_at: string;
refreshed_at: string | null;
user_agent: string | null;
ip: unknown;
not_after: string | null;
aal: 'aal1' | 'aal2' | 'aal3' | null;
}
export function SecurityTab() {
@@ -49,7 +44,7 @@ export function SecurityTab() {
const [disconnectingProvider, setDisconnectingProvider] = useState<OAuthProvider | null>(null);
const [hasPassword, setHasPassword] = useState(false);
const [addingPassword, setAddingPassword] = useState(false);
const [sessions, setSessions] = useState<UserSession[]>([]);
const [sessions, setSessions] = useState<AuthSession[]>([]);
const [loadingSessions, setLoadingSessions] = useState(true);
// Load user identities on mount
@@ -183,33 +178,23 @@ export function SecurityTab() {
const fetchSessions = async () => {
if (!user) return;
const { data, error } = await supabase
.from('user_sessions')
.select('*')
.eq('user_id', user.id)
.order('last_activity', { ascending: false });
const { data, error } = await supabase.rpc('get_my_sessions');
if (error) {
console.error('Error fetching sessions:', error);
toast({
title: 'Error',
description: 'Failed to load sessions',
variant: 'destructive'
});
} else {
const typedSessions: UserSession[] = (data || []).map(session => ({
id: session.id,
device_info: session.device_info as DeviceInfo | null,
last_activity: session.last_activity,
created_at: session.created_at,
expires_at: session.expires_at,
session_token: session.session_token
}));
setSessions(typedSessions);
setSessions(data || []);
}
setLoadingSessions(false);
};
const revokeSession = async (sessionId: string) => {
const { error } = await supabase
.from('user_sessions')
.delete()
.eq('id', sessionId);
const { error } = await supabase.rpc('revoke_my_session', { session_id: sessionId });
if (error) {
toast({
@@ -226,13 +211,30 @@ export function SecurityTab() {
}
};
const getDeviceIcon = (deviceInfo: DeviceInfo | null) => {
const ua = deviceInfo?.userAgent?.toLowerCase() || '';
if (ua.includes('mobile')) return <Smartphone className="w-4 h-4" />;
if (ua.includes('tablet')) return <Tablet className="w-4 h-4" />;
const getDeviceIcon = (userAgent: string | null) => {
if (!userAgent) return <Monitor className="w-4 h-4" />;
const ua = userAgent.toLowerCase();
if (ua.includes('mobile') || ua.includes('android') || ua.includes('iphone')) {
return <Smartphone className="w-4 h-4" />;
}
if (ua.includes('tablet') || ua.includes('ipad')) {
return <Tablet className="w-4 h-4" />;
}
return <Monitor className="w-4 h-4" />;
};
const getBrowserName = (userAgent: string | null) => {
if (!userAgent) return 'Unknown Browser';
const ua = userAgent.toLowerCase();
if (ua.includes('firefox')) return 'Firefox';
if (ua.includes('chrome') && !ua.includes('edg')) return 'Chrome';
if (ua.includes('safari') && !ua.includes('chrome')) return 'Safari';
if (ua.includes('edg')) return 'Edge';
return 'Unknown Browser';
};
// Get connected accounts with identity data
const connectedAccounts = [
{
@@ -415,17 +417,21 @@ export function SecurityTab() {
{sessions.map((session) => (
<div key={session.id} className="flex items-start justify-between p-3 border rounded-lg">
<div className="flex gap-3">
{getDeviceIcon(session.device_info)}
{getDeviceIcon(session.user_agent)}
<div>
<p className="font-medium">
{session.device_info?.browser || 'Unknown Browser'}
{getBrowserName(session.user_agent)}
</p>
<p className="text-sm text-muted-foreground">
Last active: {format(new Date(session.last_activity), 'PPpp')}
</p>
<p className="text-sm text-muted-foreground">
Expires: {format(new Date(session.expires_at), 'PPpp')}
{session.ip && `${session.ip}`}
Last active: {format(new Date(session.refreshed_at || session.created_at), 'PPpp')}
{session.aal === 'aal2' && ' • MFA'}
</p>
{session.not_after && (
<p className="text-sm text-muted-foreground">
Expires: {format(new Date(session.not_after), 'PPpp')}
</p>
)}
</div>
</div>
<Button