@main12/auth-login 0.3.0 → 0.3.2

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/index.js CHANGED
@@ -1,5 +1,5 @@
1
+ import { OAuth2Plugin } from 'payload-oauth2';
1
2
  import { authEndpoints } from './endpoints/authEndpoints.js';
2
- import { googleOAuthEndpoints } from './endpoints/googleOAuth.js';
3
3
  import { pluginConfig } from './config.js';
4
4
  function resolveGoogleConfig(providers) {
5
5
  const google = providers?.google;
@@ -11,22 +11,32 @@ function resolveGoogleConfig(providers) {
11
11
  clientSecret: ''
12
12
  };
13
13
  }
14
- // Explicit config provided
15
- if (typeof google === 'object' && google.clientId && google.clientSecret) {
14
+ // Explicit config object — user intends to enable Google OAuth
15
+ // Resolve credentials from explicit values or fall back to env vars
16
+ if (typeof google === 'object') {
17
+ const clientId = google.clientId || process.env.GOOGLE_CLIENT_ID || '';
18
+ const clientSecret = google.clientSecret || process.env.GOOGLE_CLIENT_SECRET || '';
16
19
  return {
17
20
  enabled: true,
18
- clientId: google.clientId,
19
- clientSecret: google.clientSecret
21
+ clientId,
22
+ clientSecret
20
23
  };
21
24
  }
22
- // Auto-detect from env
23
- const envId = process.env.GOOGLE_CLIENT_ID || '';
24
- const envSecret = process.env.GOOGLE_CLIENT_SECRET || '';
25
- const hasEnvCreds = !!(envId && envSecret);
25
+ // google: true — auto-detect from env
26
+ if (google === true) {
27
+ const envId = process.env.GOOGLE_CLIENT_ID || '';
28
+ const envSecret = process.env.GOOGLE_CLIENT_SECRET || '';
29
+ return {
30
+ enabled: !!(envId && envSecret),
31
+ clientId: envId,
32
+ clientSecret: envSecret
33
+ };
34
+ }
35
+ // Not configured at all — disabled
26
36
  return {
27
- enabled: hasEnvCreds,
28
- clientId: envId,
29
- clientSecret: envSecret
37
+ enabled: false,
38
+ clientId: '',
39
+ clientSecret: ''
30
40
  };
31
41
  }
32
42
  /**
@@ -38,7 +48,7 @@ function resolveGoogleConfig(providers) {
38
48
  process.env.GOOGLE_CLIENT_SECRET = googleConfig.clientSecret;
39
49
  }
40
50
  }
41
- export const authLoginPlugin = (options = {})=>(config)=>{
51
+ export const authLoginPlugin = (options = {})=>async (config)=>{
42
52
  if (options.enabled === false) return config;
43
53
  // Resolve provider config
44
54
  const googleConfig = resolveGoogleConfig(options.providers);
@@ -51,6 +61,15 @@ export const authLoginPlugin = (options = {})=>(config)=>{
51
61
  pluginConfig.Logo = options.logo;
52
62
  }
53
63
  pluginConfig.googleOAuthEnabled = googleConfig.enabled;
64
+ // Set env vars so RSC components can read config across module boundaries
65
+ process.env.AUTH_PLUGIN_STYLE = pluginConfig.style;
66
+ process.env.AUTH_PLUGIN_GOOGLE_OAUTH = String(pluginConfig.googleOAuthEnabled);
67
+ process.env.AUTH_PLUGIN_ALLOW_SIGNUP = String(options.allowSignup !== false);
68
+ process.env.AUTH_PLUGIN_PASSWORD_LOGIN = String(options.passwordLogin !== false);
69
+ process.env.AUTH_PLUGIN_OTP_LOGIN = String(options.otpLogin !== false);
70
+ pluginConfig.passwordLogin = options.passwordLogin !== false;
71
+ pluginConfig.otpLogin = options.otpLogin !== false;
72
+ process.env.AUTH_PLUGIN_ROUTE_REDIRECTS = String(pluginConfig.routeRedirects);
54
73
  // Route redirects config
55
74
  if (options.routeRedirects) {
56
75
  pluginConfig.routeRedirects = true;
@@ -58,17 +77,84 @@ export const authLoginPlugin = (options = {})=>(config)=>{
58
77
  pluginConfig.authBasePath = options.routeRedirects.basePath;
59
78
  }
60
79
  }
80
+ // Register hidden auth-otps collection for OTP storage
81
+ config.collections = [
82
+ ...config.collections || [],
83
+ {
84
+ slug: 'auth-otps',
85
+ admin: {
86
+ hidden: true
87
+ },
88
+ fields: [
89
+ {
90
+ name: 'email',
91
+ type: 'email',
92
+ required: true,
93
+ index: true
94
+ },
95
+ {
96
+ name: 'hash',
97
+ type: 'text',
98
+ required: true
99
+ },
100
+ {
101
+ name: 'purpose',
102
+ type: 'text'
103
+ },
104
+ {
105
+ name: 'attempts',
106
+ type: 'number',
107
+ defaultValue: 0
108
+ },
109
+ {
110
+ name: 'expiresAt',
111
+ type: 'text'
112
+ }
113
+ ]
114
+ }
115
+ ];
61
116
  // Register auth API endpoints
62
117
  config.endpoints = [
63
118
  ...config.endpoints || [],
64
119
  ...authEndpoints
65
120
  ];
66
- // Register Google OAuth endpoints only if enabled
67
- if (pluginConfig.googleOAuthEnabled) {
68
- config.endpoints = [
69
- ...config.endpoints,
70
- ...googleOAuthEndpoints
71
- ];
121
+ // Register Google OAuth via payload-oauth2 if enabled
122
+ if (googleConfig.enabled) {
123
+ const googleOpts = typeof options.providers?.google === 'object' ? options.providers.google : {};
124
+ const serverURL = options.domain || process.env.NEXT_PUBLIC_SERVER_URL || 'http://localhost:3000';
125
+ config = await OAuth2Plugin({
126
+ enabled: true,
127
+ strategyName: 'google',
128
+ useEmailAsIdentity: true,
129
+ serverURL,
130
+ clientId: googleConfig.clientId,
131
+ clientSecret: googleConfig.clientSecret,
132
+ tokenEndpoint: 'https://oauth2.googleapis.com/token',
133
+ providerAuthorizationUrl: 'https://accounts.google.com/o/oauth2/v2/auth',
134
+ scopes: [
135
+ 'openid',
136
+ 'https://www.googleapis.com/auth/userinfo.email',
137
+ 'https://www.googleapis.com/auth/userinfo.profile'
138
+ ],
139
+ authorizePath: '/oauth/google',
140
+ callbackPath: '/oauth/google/callback',
141
+ prompt: googleOpts.prompt || 'select_account',
142
+ getUserInfo: async (accessToken)=>{
143
+ const response = await fetch('https://www.googleapis.com/oauth2/v3/userinfo', {
144
+ headers: {
145
+ Authorization: `Bearer ${accessToken}`
146
+ }
147
+ });
148
+ const user = await response.json();
149
+ return {
150
+ email: user.email,
151
+ sub: user.sub,
152
+ name: user.name
153
+ };
154
+ },
155
+ successRedirect: ()=>googleOpts.successRedirect || '/admin',
156
+ failureRedirect: ()=>googleOpts.failureRedirect || '/login?error=Google login failed'
157
+ })(config);
72
158
  }
73
159
  // Chain onInit
74
160
  const incomingOnInit = config.onInit;
package/dist/proxy.js CHANGED
@@ -29,11 +29,25 @@ const AUTH_ROUTES = [
29
29
  const base = (basePath ?? pluginConfig.authBasePath).replace(/\/$/, '');
30
30
  const routeSet = new Set(routes);
31
31
  return function proxy(request) {
32
- // If routeRedirects is disabled in plugin config, pass through
33
- if (!pluginConfig.routeRedirects && !basePath) {
32
+ // Check route redirects — use env var (set by plugin) as it survives module boundaries
33
+ const redirectsEnabled = pluginConfig.routeRedirects || process.env.AUTH_PLUGIN_ROUTE_REDIRECTS === 'true';
34
+ if (!redirectsEnabled && !basePath) {
34
35
  return NextResponse.next();
35
36
  }
36
37
  const { pathname } = request.nextUrl;
38
+ // If user is already logged in, redirect away from auth pages (login, signup, etc.)
39
+ const token = request.cookies.get('payload-token')?.value;
40
+ if (token) {
41
+ const segment = pathname.replace(/^\//, '').replace(/\/$/, '');
42
+ const isAuthPage = routeSet.has(segment) || routeSet.has(pathname.replace(`${base}/`, ''));
43
+ if (isAuthPage || pathname.startsWith(`${base}/login`) || pathname.startsWith(`${base}/signup`)) {
44
+ const redirect = request.nextUrl.searchParams.get('redirect') || '/';
45
+ const url = request.nextUrl.clone();
46
+ url.pathname = redirect;
47
+ url.search = '';
48
+ return NextResponse.redirect(url);
49
+ }
50
+ }
37
51
  // Always redirect /admin/login to the plugin login page
38
52
  if (pathname === '/admin/login' || pathname === '/admin/login/') {
39
53
  const url = request.nextUrl.clone();
@@ -57,6 +71,7 @@ export const config = {
57
71
  '/signup',
58
72
  '/forgot-password',
59
73
  '/verify-otp',
60
- '/set-password'
74
+ '/set-password',
75
+ '/auth/:path*'
61
76
  ]
62
77
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@main12/auth-login",
3
- "version": "0.3.0",
3
+ "version": "0.3.2",
4
4
  "description": "Reusable Payload CMS auth plugin — login, signup, OTP, forgot password, branded emails, Powered by Main12",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -19,11 +19,6 @@
19
19
  "import": "./dist/exports/rsc.js",
20
20
  "types": "./dist/exports/rsc.d.ts",
21
21
  "default": "./dist/exports/rsc.js"
22
- },
23
- "./proxy": {
24
- "import": "./dist/proxy.js",
25
- "types": "./dist/proxy.d.ts",
26
- "default": "./dist/proxy.js"
27
22
  }
28
23
  },
29
24
  "main": "./dist/index.js",
@@ -31,21 +26,6 @@
31
26
  "files": [
32
27
  "dist"
33
28
  ],
34
- "scripts": {
35
- "build": "pnpm copyfiles && pnpm build:types && pnpm build:swc && pnpm build:fix-esm-imports",
36
- "build:fix-esm-imports": "node ./scripts/fix-esm-imports.mjs",
37
- "build:swc": "swc ./src -d ./dist --config-file .swcrc --strip-leading-paths",
38
- "build:types": "tsc --outDir dist",
39
- "clean": "rimraf {dist,*.tsbuildinfo}",
40
- "copyfiles": "copyfiles -u 1 \"src/**/*.{html,css,scss,ttf,woff,woff2,eot,svg,jpg,png,json}\" dist/",
41
- "dev": "next dev dev --turbo",
42
- "dev:generate-importmap": "pnpm dev:payload generate:importmap",
43
- "dev:generate-types": "pnpm dev:payload generate:types",
44
- "dev:payload": "cross-env PAYLOAD_CONFIG_PATH=./dev/payload.config.ts payload",
45
- "lint": "eslint",
46
- "lint:fix": "eslint ./src --fix",
47
- "test": "vitest"
48
- },
49
29
  "devDependencies": {
50
30
  "@eslint/eslintrc": "^3.2.0",
51
31
  "@heroui/react": "^3.2.2",
@@ -95,33 +75,26 @@
95
75
  "engines": {
96
76
  "node": ">=18.20.2"
97
77
  },
98
- "publishConfig": {
99
- "exports": {
100
- ".": {
101
- "import": "./dist/index.js",
102
- "types": "./dist/index.d.ts",
103
- "default": "./dist/index.js"
104
- },
105
- "./client": {
106
- "import": "./dist/exports/client.js",
107
- "types": "./dist/exports/client.d.ts",
108
- "default": "./dist/exports/client.js"
109
- },
110
- "./rsc": {
111
- "import": "./dist/exports/rsc.js",
112
- "types": "./dist/exports/rsc.d.ts",
113
- "default": "./dist/exports/rsc.js"
114
- }
115
- },
116
- "main": "./dist/index.js",
117
- "types": "./dist/index.d.ts"
118
- },
119
- "pnpm": {
120
- "onlyBuiltDependencies": [
121
- "@swc/core",
122
- "sharp",
123
- "esbuild"
124
- ]
78
+ "registry": "https://registry.npmjs.org/",
79
+ "dependencies": {
80
+ "@main12/brevo-adapter": "^0.1.1",
81
+ "axios": "^1.20.0",
82
+ "jose": "^6.2.12",
83
+ "payload-oauth2": "^1.0.21"
125
84
  },
126
- "registry": "https://registry.npmjs.org/"
127
- }
85
+ "scripts": {
86
+ "build": "pnpm copyfiles && pnpm build:types && pnpm build:swc && pnpm build:fix-esm-imports",
87
+ "build:fix-esm-imports": "node ./scripts/fix-esm-imports.mjs",
88
+ "build:swc": "swc ./src -d ./dist --config-file .swcrc --strip-leading-paths",
89
+ "build:types": "tsc --outDir dist",
90
+ "clean": "rimraf {dist,*.tsbuildinfo}",
91
+ "copyfiles": "copyfiles -u 1 \"src/**/*.{html,css,scss,ttf,woff,woff2,eot,svg,jpg,png,json}\" dist/",
92
+ "dev": "next dev dev --turbo",
93
+ "dev:generate-importmap": "pnpm dev:payload generate:importmap",
94
+ "dev:generate-types": "pnpm dev:payload generate:types",
95
+ "dev:payload": "cross-env PAYLOAD_CONFIG_PATH=./dev/payload.config.ts payload",
96
+ "lint": "eslint",
97
+ "lint:fix": "eslint ./src --fix",
98
+ "test": "vitest"
99
+ }
100
+ }
@@ -1,10 +0,0 @@
1
- import type { Endpoint } from 'payload';
2
- /**
3
- * GET /api/auth/oauth/google — start Google OAuth flow
4
- */
5
- export declare const googleOAuthStart: Endpoint;
6
- /**
7
- * GET /api/auth/oauth/google/callback — handle Google's redirect back
8
- */
9
- export declare const googleOAuthCallback: Endpoint;
10
- export declare const googleOAuthEndpoints: Endpoint[];
@@ -1,144 +0,0 @@
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
- ];