-
Notifications
You must be signed in to change notification settings - Fork 8
Expand file tree
/
Copy pathauth.ts
More file actions
160 lines (142 loc) · 5.79 KB
/
auth.ts
File metadata and controls
160 lines (142 loc) · 5.79 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
import NextAuth, { type DefaultSession, type NextAuthConfig } from 'next-auth';
import Google from 'next-auth/providers/google';
import GitHub from 'next-auth/providers/github';
import Credentials from 'next-auth/providers/credentials';
import type { JWT } from 'next-auth/jwt';
import { fetchFromServiceAPI } from '@/lib/api';
declare module 'next-auth' {
interface Session {
user: {
id: string;
plan: 'free' | 'pro';
deletion_status?: 'none' | 'scheduled' | 'permanent';
deletion_scheduled_at?: string | null;
can_restore_until?: string | null;
banStatus?: 'none' | 'warned' | 'banned';
banReason?: string | null;
banAt?: string | null;
contactEmail?: string;
contactNote?: string;
} & DefaultSession['user'];
}
interface User {
plan?: 'free' | 'pro';
banStatus?: 'none' | 'warned' | 'banned';
}
}
declare module 'next-auth/jwt' {
interface JWT {
id: string;
plan: 'free' | 'pro';
deletion_status?: 'none' | 'scheduled' | 'permanent';
deletion_scheduled_at?: string | null;
can_restore_until?: string | null;
banStatus?: 'none' | 'warned' | 'banned';
banReason?: string | null;
banAt?: string | null;
contactEmail?: string;
contactNote?: string;
last_synced_at?: number;
}
}
async function upsertUser(user: { id: string; email?: string | null; name?: string | null }) {
const resolvedName = user.name?.trim() || user.email?.split('@')[0] || user.id;
const resolvedEmail = user.email?.trim() || `${user.id}@provider.noemail`;
try {
const { sign } = await import('@/lib/jwt');
const signedToken = await sign({ id: user.id, plan: 'free' });
await fetchFromServiceAPI('/auth/upsert-user', {
method: 'POST',
headers: { 'Authorization': `Bearer ${signedToken}` },
body: JSON.stringify({ wyiUserId: user.id, email: resolvedEmail, name: resolvedName, plan: 'free' }),
}, { id: user.id });
} catch (e) {
console.error('Upsert failed:', e);
}
}
const config: NextAuthConfig = {
session: { strategy: 'jwt' },
providers: [
Google({ clientId: process.env.GOOGLE_CLIENT_ID!, clientSecret: process.env.GOOGLE_CLIENT_SECRET! }),
GitHub({
clientId: process.env.GITHUB_ID!,
clientSecret: process.env.GITHUB_SECRET!,
authorization: { params: { scope: 'read:user user:email' } },
profile(profile) {
return {
id: String(profile.id), name: profile.name ?? profile.login, email: profile.email, image: profile.avatar_url,
};
},
}),
Credentials({
id: 'magic-link',
name: 'Magic Link',
credentials: { email: { type: 'text' }, magicVerified: { type: 'text' } },
async authorize(credentials) {
if (credentials?.magicVerified !== 'true' || !credentials?.email) return null;
const email = credentials.email as string;
return { id: email, email, name: email.split('@')[0] };
},
}),
],
secret: process.env.AUTH_SECRET,
trustHost: true,
callbacks: {
async signIn({ user }) {
upsertUser({ id: user.id!, email: user.email, name: user.name });
return true;
},
async jwt({ token, user, trigger }) {
if (user) {
token.id = user.id!;
token.plan = (user as any).plan ?? 'free';
}
const now = Date.now();
const CACHE_DURATION_MS = 5 * 60 * 1000;
const needsSync =
trigger === 'signIn' ||
trigger === 'update' ||
!token.last_synced_at ||
(now - token.last_synced_at > CACHE_DURATION_MS);
if (token.id && needsSync) {
try {
const { sign } = await import('@/lib/jwt');
const signedToken = await sign({ id: token.id, plan: token.plan });
const updatedUser = await fetchFromServiceAPI('/user/status', {
method: 'POST',
headers: { 'Authorization': `Bearer ${signedToken}` },
body: JSON.stringify({ userId: token.id }),
cache: 'no-store' // <--- CRITICAL FIX: Disables Next.js fetch caching
}, { id: token.id });
if (updatedUser?.plan) token.plan = updatedUser.plan;
token.deletion_status = updatedUser?.deletion_status ?? 'none';
token.deletion_scheduled_at = updatedUser?.deletion_scheduled_at ?? null;
token.can_restore_until = updatedUser?.can_restore_until ?? null;
token.banStatus = updatedUser?.banStatus ?? 'none';
token.banReason = updatedUser?.banReason ?? null;
token.banAt = updatedUser?.banAt ?? null;
token.contactEmail = updatedUser?.contactEmail ?? 'support@freecustom.email';
token.contactNote = updatedUser?.contactNote ?? 'To appeal this decision, email us with your account details. Bans are reviewed within 5 business days.';
token.last_synced_at = now;
} catch (e) {
console.error('JWT sync failed:', e);
}
}
return token;
},
async session({ session, token }) {
session.user.id = token.id;
session.user.plan = token.plan ?? 'free';
session.user.deletion_status = token.deletion_status ?? 'none';
session.user.deletion_scheduled_at = token.deletion_scheduled_at ?? null;
session.user.can_restore_until = token.can_restore_until ?? null;
session.user.banStatus = token.banStatus ?? 'none';
session.user.banReason = token.banReason ?? null;
session.user.banAt = token.banAt ?? null;
session.user.contactEmail = token.contactEmail ?? 'support@freecustom.email';
session.user.contactNote = token.contactNote ?? 'To appeal this decision, email us with your account details. Bans are reviewed within 5 business days.';
return session;
},
},
};
export const { handlers, auth, signIn, signOut } = NextAuth(config);