@main12/auth-login 0.1.6 → 0.1.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/endpoints/googleOAuth.js +144 -0
- package/dist/index.js +8 -0
- package/package.json +1 -1
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Google OAuth 2.0 — zero dependencies, pure REST.
|
|
3
|
+
*
|
|
4
|
+
* Flow:
|
|
5
|
+
* 1. GET /api/auth/oauth/google → Google consent screen
|
|
6
|
+
* 2. GET /api/auth/oauth/google/callback → exchange code, get profile, login, redirect
|
|
7
|
+
*
|
|
8
|
+
* Requires env vars: GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET
|
|
9
|
+
*/ const GOOGLE_AUTH_URL = 'https://accounts.google.com/o/oauth2/v2/auth';
|
|
10
|
+
const GOOGLE_TOKEN_URL = 'https://oauth2.googleapis.com/token';
|
|
11
|
+
const GOOGLE_USERINFO_URL = 'https://www.googleapis.com/oauth2/v3/userinfo';
|
|
12
|
+
function env(key, fb = '') {
|
|
13
|
+
return process.env[key] || fb;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* GET /api/auth/oauth/google — start Google OAuth flow
|
|
17
|
+
*/ export const googleOAuthStart = {
|
|
18
|
+
path: '/api/auth/oauth/google',
|
|
19
|
+
method: 'get',
|
|
20
|
+
handler: async (req)=>{
|
|
21
|
+
const clientId = env('GOOGLE_CLIENT_ID');
|
|
22
|
+
if (!clientId) return Response.json({
|
|
23
|
+
error: 'Google OAuth not configured'
|
|
24
|
+
}, {
|
|
25
|
+
status: 501
|
|
26
|
+
});
|
|
27
|
+
const url = new URL(req.url || 'http://localhost');
|
|
28
|
+
const redirect = url.searchParams.get('redirect') || '/';
|
|
29
|
+
const base = env('NEXT_PUBLIC_SERVER_URL', 'http://localhost:3000');
|
|
30
|
+
const params = new URLSearchParams({
|
|
31
|
+
client_id: clientId,
|
|
32
|
+
redirect_uri: `${base}/api/auth/oauth/google/callback`,
|
|
33
|
+
response_type: 'code',
|
|
34
|
+
scope: 'openid email profile',
|
|
35
|
+
access_type: 'online',
|
|
36
|
+
state: encodeURIComponent(redirect)
|
|
37
|
+
});
|
|
38
|
+
return Response.redirect(`${GOOGLE_AUTH_URL}?${params.toString()}`, 302);
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
/**
|
|
42
|
+
* GET /api/auth/oauth/google/callback — handle Google's redirect back
|
|
43
|
+
*/ export const googleOAuthCallback = {
|
|
44
|
+
path: '/api/auth/oauth/google/callback',
|
|
45
|
+
method: 'get',
|
|
46
|
+
handler: async (req)=>{
|
|
47
|
+
const url = new URL(req.url || 'http://localhost');
|
|
48
|
+
const code = url.searchParams.get('code');
|
|
49
|
+
const state = url.searchParams.get('state') || '/';
|
|
50
|
+
const redirectTo = decodeURIComponent(state);
|
|
51
|
+
if (!code) return Response.redirect(`/login?error=${url.searchParams.get('error') || 'oauth_failed'}`, 302);
|
|
52
|
+
const clientId = env('GOOGLE_CLIENT_ID');
|
|
53
|
+
const clientSecret = env('GOOGLE_CLIENT_SECRET');
|
|
54
|
+
const base = env('NEXT_PUBLIC_SERVER_URL', 'http://localhost:3000');
|
|
55
|
+
try {
|
|
56
|
+
// 1. Exchange code for access token
|
|
57
|
+
const tokenRes = await fetch(GOOGLE_TOKEN_URL, {
|
|
58
|
+
method: 'POST',
|
|
59
|
+
headers: {
|
|
60
|
+
'Content-Type': 'application/x-www-form-urlencoded'
|
|
61
|
+
},
|
|
62
|
+
body: new URLSearchParams({
|
|
63
|
+
code,
|
|
64
|
+
client_id: clientId,
|
|
65
|
+
client_secret: clientSecret,
|
|
66
|
+
redirect_uri: `${base}/api/auth/oauth/google/callback`,
|
|
67
|
+
grant_type: 'authorization_code'
|
|
68
|
+
})
|
|
69
|
+
});
|
|
70
|
+
if (!tokenRes.ok) return Response.redirect(`/login?error=oauth_failed`, 302);
|
|
71
|
+
const tokens = await tokenRes.json();
|
|
72
|
+
const accessToken = tokens.access_token;
|
|
73
|
+
// 2. Get Google profile
|
|
74
|
+
const profileRes = await fetch(GOOGLE_USERINFO_URL, {
|
|
75
|
+
headers: {
|
|
76
|
+
Authorization: `Bearer ${accessToken}`
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
if (!profileRes.ok) return Response.redirect(`/login?error=oauth_failed`, 302);
|
|
80
|
+
const profile = await profileRes.json();
|
|
81
|
+
const email = profile.email?.toLowerCase();
|
|
82
|
+
const name = profile.name || email?.split('@')[0];
|
|
83
|
+
if (!email) return Response.redirect(`/login?error=oauth_failed`, 302);
|
|
84
|
+
// 3. Find or create user
|
|
85
|
+
const users = await req.payload.find({
|
|
86
|
+
collection: 'users',
|
|
87
|
+
where: {
|
|
88
|
+
email: {
|
|
89
|
+
equals: email
|
|
90
|
+
}
|
|
91
|
+
},
|
|
92
|
+
limit: 1
|
|
93
|
+
});
|
|
94
|
+
let userId;
|
|
95
|
+
let userPw;
|
|
96
|
+
if (users.docs?.length) {
|
|
97
|
+
userId = users.docs[0].id;
|
|
98
|
+
// Set a known temp password so we can login
|
|
99
|
+
userPw = `g_tmp_${Date.now()}`;
|
|
100
|
+
await req.payload.update({
|
|
101
|
+
collection: 'users',
|
|
102
|
+
id: userId,
|
|
103
|
+
data: {
|
|
104
|
+
password: userPw
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
} else {
|
|
108
|
+
userPw = `g_new_${Date.now()}`;
|
|
109
|
+
const newUser = await req.payload.create({
|
|
110
|
+
collection: 'users',
|
|
111
|
+
data: {
|
|
112
|
+
email,
|
|
113
|
+
name,
|
|
114
|
+
password: userPw,
|
|
115
|
+
authProvider: 'google'
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
userId = newUser.id;
|
|
119
|
+
}
|
|
120
|
+
// 4. Login
|
|
121
|
+
const loginResult = await req.payload.login({
|
|
122
|
+
collection: 'users',
|
|
123
|
+
data: {
|
|
124
|
+
email,
|
|
125
|
+
password: userPw
|
|
126
|
+
},
|
|
127
|
+
req: req
|
|
128
|
+
});
|
|
129
|
+
// 5. Set cookie + redirect
|
|
130
|
+
const response = Response.redirect(redirectTo, 302);
|
|
131
|
+
if (loginResult.token) {
|
|
132
|
+
response.headers.set('Set-Cookie', `payload-token=${loginResult.token}; Path=/; HttpOnly; SameSite=Lax` + `${process.env.NODE_ENV === 'production' ? '; Secure' : ''}` + `; Max-Age=${loginResult.exp || 7200}`);
|
|
133
|
+
}
|
|
134
|
+
return response;
|
|
135
|
+
} catch (err) {
|
|
136
|
+
console.error('[auth-login] Google OAuth error:', err);
|
|
137
|
+
return Response.redirect(`/login?error=oauth_failed`, 302);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
export const googleOAuthEndpoints = [
|
|
142
|
+
googleOAuthStart,
|
|
143
|
+
googleOAuthCallback
|
|
144
|
+
];
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { authEndpoints } from './endpoints/authEndpoints.js';
|
|
2
|
+
import { googleOAuthEndpoints } from './endpoints/googleOAuth.js';
|
|
2
3
|
import { pluginConfig } from './config.js';
|
|
3
4
|
export const authLoginPlugin = (options = {})=>(config)=>{
|
|
4
5
|
if (options.enabled === false) return config;
|
|
@@ -10,6 +11,13 @@ export const authLoginPlugin = (options = {})=>(config)=>{
|
|
|
10
11
|
...config.endpoints || [],
|
|
11
12
|
...authEndpoints
|
|
12
13
|
];
|
|
14
|
+
// Register Google OAuth endpoints if enabled
|
|
15
|
+
if (options.googleOAuth !== false) {
|
|
16
|
+
config.endpoints = [
|
|
17
|
+
...config.endpoints,
|
|
18
|
+
...googleOAuthEndpoints
|
|
19
|
+
];
|
|
20
|
+
}
|
|
13
21
|
// Chain onInit
|
|
14
22
|
const incomingOnInit = config.onInit;
|
|
15
23
|
config.onInit = async (payload)=>{
|
package/package.json
CHANGED