@maka/maka-cli 5.1.46 → 5.1.47
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/bundle/typescript/package.json +1 -1
- package/bundle/typescript/src/generators/kits/auth/auth0-meteor.auth.gen.js +42 -1
- package/bundle/typescript/src/generators/kits/auth/auth0-meteor.auth.gen.js.map +1 -1
- package/bundle/typescript/src/templates/react/kit/auth/service/auth0-client.js.jsx +22 -0
- package/bundle/typescript/src/templates/react/kit/auth/service/auth0-client.js.tsx +22 -0
- package/bundle/typescript/src/templates/react/kit/auth/service/auth0-config.js.jsx +17 -4
- package/bundle/typescript/src/templates/react/kit/auth/service/auth0-config.js.tsx +17 -4
- package/bundle/typescript/src/templates/react/kit/auth/service/auth0-methods.js.jsx +127 -8
- package/bundle/typescript/src/templates/react/kit/auth/service/auth0-methods.js.tsx +127 -8
- package/bundle/typescript/src/templates/react/kit/auth/service/auth0-validator.js.jsx +54 -18
- package/bundle/typescript/src/templates/react/kit/auth/service/auth0-validator.js.tsx +54 -18
- package/package.json +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@maka/maka-cli",
|
|
3
|
-
"version": "5.1.
|
|
3
|
+
"version": "5.1.47",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"summary": "A command line tool for scaffolding Meteor 2.x applications using either React.",
|
|
6
6
|
"description": "A command line tool for scaffolding Meteor 2.x applications using React.",
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import path from 'path';
|
|
2
|
+
import fs from 'fs';
|
|
2
3
|
import { Generator } from '../../../generator.js';
|
|
3
4
|
import { Log } from '../../../tools/log/log.class.js';
|
|
4
5
|
import AuthGen from './auth.generator.js';
|
|
@@ -148,6 +149,7 @@ Generator.create({
|
|
|
148
149
|
handleAuth0Callback,
|
|
149
150
|
logoutAuth0,
|
|
150
151
|
getAuth0IdToken,
|
|
152
|
+
refreshAuth0Token,
|
|
151
153
|
} from './auth0-client';
|
|
152
154
|
`
|
|
153
155
|
});
|
|
@@ -272,6 +274,26 @@ Generator.create({
|
|
|
272
274
|
throw error;
|
|
273
275
|
}
|
|
274
276
|
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Silently refresh the Auth0 token and update Meteor session
|
|
280
|
+
* This is called automatically when the token is about to expire
|
|
281
|
+
* Returns the updated expiry time
|
|
282
|
+
*/
|
|
283
|
+
static async refreshAuth0Token() {
|
|
284
|
+
try {
|
|
285
|
+
// Get fresh token from Auth0 silently (using refresh token)
|
|
286
|
+
const idToken = await refreshAuth0Token();
|
|
287
|
+
|
|
288
|
+
// Call Meteor method to validate and update token
|
|
289
|
+
const result = await Meteor.callAsync('auth0.refreshToken', idToken);
|
|
290
|
+
|
|
291
|
+
return result;
|
|
292
|
+
} catch (error) {
|
|
293
|
+
console.error('Auth0 token refresh failed:', error);
|
|
294
|
+
throw error;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
275
297
|
`;
|
|
276
298
|
await scaffold.injectIntoFile({
|
|
277
299
|
filePath: `${pathToAuthService}.${jsEngine}`,
|
|
@@ -310,6 +332,15 @@ Generator.create({
|
|
|
310
332
|
end: '\\}\\), \\[',
|
|
311
333
|
replace: false
|
|
312
334
|
});
|
|
335
|
+
// Comment out the placeholder token validation in useEffect
|
|
336
|
+
// This prevents "Login failed: Invalid token" errors when using Meteor authentication
|
|
337
|
+
const authProviderFilePath = `${pathToAuthProvider}.${jsEngine}`;
|
|
338
|
+
const authProviderContent = fs.readFileSync(authProviderFilePath, 'utf8');
|
|
339
|
+
// Replace the loginUserWithToken call with a comment explaining Meteor handles this
|
|
340
|
+
const updatedContent = authProviderContent
|
|
341
|
+
.replace(/const renewedUser = await loginUserWithToken\(\);/g, '// Auth0/Meteor handles token validation automatically\n // const renewedUser = await loginUserWithToken();')
|
|
342
|
+
.replace(/if \(renewedUser\) \{[\s\S]*?setUserAuthorizations\(\[\]\);[\s\S]*?\}/, '// Meteor.user() is managed by Meteor\'s accounts system\n // Token validation is handled by Meteor');
|
|
343
|
+
fs.writeFileSync(authProviderFilePath, updatedContent, 'utf8');
|
|
313
344
|
// ===================================================================================
|
|
314
345
|
// 7. Import auth0-methods on server startup
|
|
315
346
|
// ===================================================================================
|
|
@@ -599,11 +630,21 @@ When a user logs in with Auth0, their Meteor user document includes:
|
|
|
599
630
|
|
|
600
631
|
## Troubleshooting
|
|
601
632
|
|
|
602
|
-
### "Invalid token" Error
|
|
633
|
+
### "Login failed: Invalid token" Error After Successful Login
|
|
634
|
+
|
|
635
|
+
If you see this error message even though you're logged in, it's because the placeholder token validation code is still running. The Auth0 kit automatically comments out this code, but if you see this error:
|
|
636
|
+
|
|
637
|
+
1. Check your \`authentication-provider.${jsEngine}\` file
|
|
638
|
+
2. Look for the \`loginUserWithToken\` call in the \`useEffect\` hook
|
|
639
|
+
3. Ensure it's commented out (the generator does this automatically)
|
|
640
|
+
4. Meteor's authentication system handles token validation automatically
|
|
641
|
+
|
|
642
|
+
### "Invalid token" Error During Login
|
|
603
643
|
|
|
604
644
|
- Check that your Auth0 domain and clientId are correct
|
|
605
645
|
- Verify the token hasn't expired
|
|
606
646
|
- Ensure your Auth0 application settings are configured correctly
|
|
647
|
+
- Make sure the domain in settings.json doesn't include \`https://\` (the kit handles this automatically)
|
|
607
648
|
|
|
608
649
|
### "Auth0 configuration missing" Error
|
|
609
650
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth0-meteor.auth.gen.js","sourceRoot":"","sources":["../../../../../../src/generators/kits/auth/auth0-meteor.auth.gen.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"auth0-meteor.auth.gen.js","sourceRoot":"","sources":["../../../../../../src/generators/kits/auth/auth0-meteor.auth.gen.ts"],"names":[],"mappings":"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"}
|
|
@@ -139,3 +139,25 @@ export const getAuth0IdToken = async () => {
|
|
|
139
139
|
|
|
140
140
|
return token.__raw;
|
|
141
141
|
};
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Silently refresh the Auth0 token and update Meteor session
|
|
145
|
+
* This is called automatically when the token is about to expire
|
|
146
|
+
*/
|
|
147
|
+
export const refreshAuth0Token = async () => {
|
|
148
|
+
const client = await getAuth0Client();
|
|
149
|
+
|
|
150
|
+
// Get a fresh token silently (using refresh token)
|
|
151
|
+
await client.getTokenSilently({
|
|
152
|
+
cacheMode: 'off', // Force refresh
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
// Get the new ID token
|
|
156
|
+
const token = await client.getIdTokenClaims();
|
|
157
|
+
|
|
158
|
+
if (!token?.__raw) {
|
|
159
|
+
throw new Error('Failed to refresh Auth0 token');
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
return token.__raw;
|
|
163
|
+
};
|
|
@@ -139,3 +139,25 @@ export const getAuth0IdToken = async (): Promise<string> => {
|
|
|
139
139
|
|
|
140
140
|
return token.__raw;
|
|
141
141
|
};
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Silently refresh the Auth0 token and update Meteor session
|
|
145
|
+
* This is called automatically when the token is about to expire
|
|
146
|
+
*/
|
|
147
|
+
export const refreshAuth0Token = async (): Promise<string> => {
|
|
148
|
+
const client = await getAuth0Client();
|
|
149
|
+
|
|
150
|
+
// Get a fresh token silently (using refresh token)
|
|
151
|
+
await client.getTokenSilently({
|
|
152
|
+
cacheMode: 'off', // Force refresh
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
// Get the new ID token
|
|
156
|
+
const token = await client.getIdTokenClaims();
|
|
157
|
+
|
|
158
|
+
if (!token?.__raw) {
|
|
159
|
+
throw new Error('Failed to refresh Auth0 token');
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
return token.__raw;
|
|
163
|
+
};
|
|
@@ -5,6 +5,15 @@
|
|
|
5
5
|
* Set your Auth0 credentials in Meteor settings or environment variables.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
+
/**
|
|
9
|
+
* Normalize Auth0 domain by removing protocol and trailing slashes
|
|
10
|
+
*/
|
|
11
|
+
const normalizeDomain = (domain) => {
|
|
12
|
+
return domain
|
|
13
|
+
.replace(/^https?:\/\//, '') // Remove http:// or https://
|
|
14
|
+
.replace(/\/$/, ''); // Remove trailing slash
|
|
15
|
+
};
|
|
16
|
+
|
|
8
17
|
/**
|
|
9
18
|
* Get Auth0 configuration from Meteor settings or environment variables
|
|
10
19
|
*/
|
|
@@ -18,10 +27,12 @@ export const getAuth0Config = () => {
|
|
|
18
27
|
);
|
|
19
28
|
}
|
|
20
29
|
|
|
30
|
+
const normalizedDomain = normalizeDomain(settings.domain);
|
|
31
|
+
|
|
21
32
|
return {
|
|
22
|
-
domain:
|
|
33
|
+
domain: normalizedDomain,
|
|
23
34
|
clientId: settings.clientId,
|
|
24
|
-
audience: settings.audience || `https://${
|
|
35
|
+
audience: settings.audience || `https://${normalizedDomain}/api/v2/`,
|
|
25
36
|
redirectUri: settings.redirectUri || window.location.origin + '/auth/callback',
|
|
26
37
|
scope: settings.scope || 'openid profile email',
|
|
27
38
|
};
|
|
@@ -36,10 +47,12 @@ export const getAuth0Config = () => {
|
|
|
36
47
|
);
|
|
37
48
|
}
|
|
38
49
|
|
|
50
|
+
const normalizedDomain = normalizeDomain(settings.domain);
|
|
51
|
+
|
|
39
52
|
return {
|
|
40
|
-
domain:
|
|
53
|
+
domain: normalizedDomain,
|
|
41
54
|
clientId: settings.clientId,
|
|
42
|
-
audience: settings.audience || `https://${
|
|
55
|
+
audience: settings.audience || `https://${normalizedDomain}/api/v2/`,
|
|
43
56
|
redirectUri: settings.redirectUri || Meteor.absoluteUrl('auth/callback'),
|
|
44
57
|
scope: settings.scope || 'openid profile email',
|
|
45
58
|
};
|
|
@@ -13,6 +13,15 @@ export interface Auth0Config {
|
|
|
13
13
|
scope: string;
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
+
/**
|
|
17
|
+
* Normalize Auth0 domain by removing protocol and trailing slashes
|
|
18
|
+
*/
|
|
19
|
+
const normalizeDomain = (domain: string): string => {
|
|
20
|
+
return domain
|
|
21
|
+
.replace(/^https?:\/\//, '') // Remove http:// or https://
|
|
22
|
+
.replace(/\/$/, ''); // Remove trailing slash
|
|
23
|
+
};
|
|
24
|
+
|
|
16
25
|
/**
|
|
17
26
|
* Get Auth0 configuration from Meteor settings or environment variables
|
|
18
27
|
*/
|
|
@@ -26,10 +35,12 @@ export const getAuth0Config = (): Auth0Config => {
|
|
|
26
35
|
);
|
|
27
36
|
}
|
|
28
37
|
|
|
38
|
+
const normalizedDomain = normalizeDomain(settings.domain);
|
|
39
|
+
|
|
29
40
|
return {
|
|
30
|
-
domain:
|
|
41
|
+
domain: normalizedDomain,
|
|
31
42
|
clientId: settings.clientId,
|
|
32
|
-
audience: settings.audience || `https://${
|
|
43
|
+
audience: settings.audience || `https://${normalizedDomain}/api/v2/`,
|
|
33
44
|
redirectUri: settings.redirectUri || window.location.origin + '/auth/callback',
|
|
34
45
|
scope: settings.scope || 'openid profile email',
|
|
35
46
|
};
|
|
@@ -44,10 +55,12 @@ export const getAuth0Config = (): Auth0Config => {
|
|
|
44
55
|
);
|
|
45
56
|
}
|
|
46
57
|
|
|
58
|
+
const normalizedDomain = normalizeDomain(settings.domain);
|
|
59
|
+
|
|
47
60
|
return {
|
|
48
|
-
domain:
|
|
61
|
+
domain: normalizedDomain,
|
|
49
62
|
clientId: settings.clientId,
|
|
50
|
-
audience: settings.audience || `https://${
|
|
63
|
+
audience: settings.audience || `https://${normalizedDomain}/api/v2/`,
|
|
51
64
|
redirectUri: settings.redirectUri || Meteor.absoluteUrl('auth/callback'),
|
|
52
65
|
scope: settings.scope || 'openid profile email',
|
|
53
66
|
};
|
|
@@ -2,6 +2,17 @@ import { Meteor } from 'meteor/meteor';
|
|
|
2
2
|
import { Accounts } from 'meteor/accounts-base';
|
|
3
3
|
import { verifyAuth0Token, findOrCreateUserFromAuth0 } from '../auth0-validator';
|
|
4
4
|
|
|
5
|
+
// Hook into Meteor logout to clean up Auth0 session
|
|
6
|
+
Accounts.onLogout((options) => {
|
|
7
|
+
const user = options.user;
|
|
8
|
+
|
|
9
|
+
// If user has Auth0 account linked, we log the logout
|
|
10
|
+
// Note: Client-side should handle Auth0 logout via auth0Client.logout()
|
|
11
|
+
if (user?.services?.auth0) {
|
|
12
|
+
console.log(`User ${user._id} logged out (Auth0 ID: ${user.services.auth0.id})`);
|
|
13
|
+
}
|
|
14
|
+
});
|
|
15
|
+
|
|
5
16
|
/**
|
|
6
17
|
* Meteor Methods for Auth0 Authentication
|
|
7
18
|
*
|
|
@@ -9,6 +20,17 @@ import { verifyAuth0Token, findOrCreateUserFromAuth0 } from '../auth0-validator'
|
|
|
9
20
|
* including token verification and user account creation/linking.
|
|
10
21
|
*/
|
|
11
22
|
|
|
23
|
+
/**
|
|
24
|
+
* Check if the user's Auth0 token has expired
|
|
25
|
+
*/
|
|
26
|
+
const isTokenExpired = (user) => {
|
|
27
|
+
const auth0Data = user?.services?.auth0;
|
|
28
|
+
if (!auth0Data?.expiresAt) {
|
|
29
|
+
return true;
|
|
30
|
+
}
|
|
31
|
+
return new Date() >= new Date(auth0Data.expiresAt);
|
|
32
|
+
};
|
|
33
|
+
|
|
12
34
|
Meteor.methods({
|
|
13
35
|
/**
|
|
14
36
|
* Authenticate user with Auth0 ID token
|
|
@@ -78,7 +100,7 @@ Meteor.methods({
|
|
|
78
100
|
const auth0Id = tokenPayload.sub;
|
|
79
101
|
|
|
80
102
|
// Check if Auth0 account is already linked to another user
|
|
81
|
-
const existingUser = Meteor.users.
|
|
103
|
+
const existingUser = await Meteor.users.findOneAsync({
|
|
82
104
|
'services.auth0.id': auth0Id,
|
|
83
105
|
_id: { $ne: this.userId },
|
|
84
106
|
});
|
|
@@ -91,15 +113,22 @@ Meteor.methods({
|
|
|
91
113
|
}
|
|
92
114
|
|
|
93
115
|
// Link Auth0 account to current user
|
|
94
|
-
Meteor.users.
|
|
116
|
+
await Meteor.users.updateAsync(this.userId, {
|
|
95
117
|
$set: {
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
email: tokenPayload.email,
|
|
118
|
+
// Profile data (client-accessible)
|
|
119
|
+
'profile.auth0': {
|
|
99
120
|
name: tokenPayload.name,
|
|
100
121
|
picture: tokenPayload.picture,
|
|
122
|
+
email: tokenPayload.email,
|
|
101
123
|
email_verified: tokenPayload.email_verified,
|
|
102
124
|
},
|
|
125
|
+
// Token data (server-only)
|
|
126
|
+
'services.auth0': {
|
|
127
|
+
id: auth0Id,
|
|
128
|
+
accessToken: tokenPayload.aud,
|
|
129
|
+
expiresAt: new Date(tokenPayload.exp * 1000),
|
|
130
|
+
issuedAt: new Date(tokenPayload.iat * 1000),
|
|
131
|
+
},
|
|
103
132
|
},
|
|
104
133
|
});
|
|
105
134
|
|
|
@@ -122,12 +151,12 @@ Meteor.methods({
|
|
|
122
151
|
/**
|
|
123
152
|
* Unlink Auth0 account from current user
|
|
124
153
|
*/
|
|
125
|
-
'auth0.unlinkAccount'() {
|
|
154
|
+
async 'auth0.unlinkAccount'() {
|
|
126
155
|
if (!this.userId) {
|
|
127
156
|
throw new Meteor.Error('not-authorized', 'Must be logged in to unlink Auth0 account');
|
|
128
157
|
}
|
|
129
158
|
|
|
130
|
-
const user = Meteor.users.
|
|
159
|
+
const user = await Meteor.users.findOneAsync(this.userId);
|
|
131
160
|
|
|
132
161
|
if (!user?.services?.auth0) {
|
|
133
162
|
throw new Meteor.Error('no-auth0-account', 'No Auth0 account linked');
|
|
@@ -141,12 +170,102 @@ Meteor.methods({
|
|
|
141
170
|
);
|
|
142
171
|
}
|
|
143
172
|
|
|
144
|
-
Meteor.users.
|
|
173
|
+
await Meteor.users.updateAsync(this.userId, {
|
|
145
174
|
$unset: {
|
|
146
175
|
'services.auth0': '',
|
|
176
|
+
'profile.auth0': '',
|
|
147
177
|
},
|
|
148
178
|
});
|
|
149
179
|
|
|
150
180
|
return { success: true };
|
|
151
181
|
},
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Check if the current user's Auth0 token is valid
|
|
185
|
+
* Returns token expiry status
|
|
186
|
+
*/
|
|
187
|
+
async 'auth0.checkTokenValidity'() {
|
|
188
|
+
if (!this.userId) {
|
|
189
|
+
throw new Meteor.Error('not-authorized', 'Must be logged in');
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const user = await Meteor.users.findOneAsync(this.userId);
|
|
193
|
+
|
|
194
|
+
if (!user?.services?.auth0) {
|
|
195
|
+
throw new Meteor.Error('no-auth0-account', 'No Auth0 account linked');
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const expired = isTokenExpired(user);
|
|
199
|
+
|
|
200
|
+
return {
|
|
201
|
+
isValid: !expired,
|
|
202
|
+
expiresAt: user.services.auth0.expiresAt,
|
|
203
|
+
issuedAt: user.services.auth0.issuedAt,
|
|
204
|
+
};
|
|
205
|
+
},
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Refresh Auth0 token and update Meteor user session
|
|
209
|
+
* This method validates the new token and updates the user's token data
|
|
210
|
+
*/
|
|
211
|
+
async 'auth0.refreshToken'(idToken) {
|
|
212
|
+
if (!this.userId) {
|
|
213
|
+
throw new Meteor.Error('not-authorized', 'Must be logged in');
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
if (!idToken || typeof idToken !== 'string') {
|
|
217
|
+
throw new Meteor.Error('invalid-token', 'Invalid token provided');
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
try {
|
|
221
|
+
// Verify the new Auth0 token
|
|
222
|
+
const tokenPayload = await verifyAuth0Token(idToken);
|
|
223
|
+
|
|
224
|
+
// Get current user
|
|
225
|
+
const user = await Meteor.users.findOneAsync(this.userId);
|
|
226
|
+
|
|
227
|
+
if (!user?.services?.auth0) {
|
|
228
|
+
throw new Meteor.Error('no-auth0-account', 'No Auth0 account linked');
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// Verify the token is for the same Auth0 user
|
|
232
|
+
if (user.services.auth0.id !== tokenPayload.sub) {
|
|
233
|
+
throw new Meteor.Error(
|
|
234
|
+
'token-mismatch',
|
|
235
|
+
'Token does not match current user Auth0 account'
|
|
236
|
+
);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// Update token data in services.auth0 (server-only)
|
|
240
|
+
await Meteor.users.updateAsync(this.userId, {
|
|
241
|
+
$set: {
|
|
242
|
+
'services.auth0.accessToken': tokenPayload.aud,
|
|
243
|
+
'services.auth0.expiresAt': new Date(tokenPayload.exp * 1000),
|
|
244
|
+
'services.auth0.issuedAt': new Date(tokenPayload.iat * 1000),
|
|
245
|
+
// Also update profile data in case it changed
|
|
246
|
+
'profile.auth0.name': tokenPayload.name,
|
|
247
|
+
'profile.auth0.picture': tokenPayload.picture,
|
|
248
|
+
'profile.auth0.email': tokenPayload.email,
|
|
249
|
+
'profile.auth0.email_verified': tokenPayload.email_verified,
|
|
250
|
+
},
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
return {
|
|
254
|
+
success: true,
|
|
255
|
+
expiresAt: new Date(tokenPayload.exp * 1000),
|
|
256
|
+
};
|
|
257
|
+
} catch (error) {
|
|
258
|
+
console.error('Auth0 token refresh error:', error);
|
|
259
|
+
|
|
260
|
+
if (error instanceof Meteor.Error) {
|
|
261
|
+
throw error;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
throw new Meteor.Error(
|
|
265
|
+
'auth0-refresh-failed',
|
|
266
|
+
'Failed to refresh Auth0 token',
|
|
267
|
+
error.message
|
|
268
|
+
);
|
|
269
|
+
}
|
|
270
|
+
},
|
|
152
271
|
});
|
|
@@ -2,6 +2,17 @@ import { Meteor } from 'meteor/meteor';
|
|
|
2
2
|
import { Accounts } from 'meteor/accounts-base';
|
|
3
3
|
import { verifyAuth0Token, findOrCreateUserFromAuth0 } from '../auth0-validator';
|
|
4
4
|
|
|
5
|
+
// Hook into Meteor logout to clean up Auth0 session
|
|
6
|
+
Accounts.onLogout((options: any) => {
|
|
7
|
+
const user = options.user;
|
|
8
|
+
|
|
9
|
+
// If user has Auth0 account linked, we log the logout
|
|
10
|
+
// Note: Client-side should handle Auth0 logout via auth0Client.logout()
|
|
11
|
+
if (user?.services?.auth0) {
|
|
12
|
+
console.log(`User ${user._id} logged out (Auth0 ID: ${user.services.auth0.id})`);
|
|
13
|
+
}
|
|
14
|
+
});
|
|
15
|
+
|
|
5
16
|
/**
|
|
6
17
|
* Meteor Methods for Auth0 Authentication
|
|
7
18
|
*
|
|
@@ -9,6 +20,17 @@ import { verifyAuth0Token, findOrCreateUserFromAuth0 } from '../auth0-validator'
|
|
|
9
20
|
* including token verification and user account creation/linking.
|
|
10
21
|
*/
|
|
11
22
|
|
|
23
|
+
/**
|
|
24
|
+
* Check if the user's Auth0 token has expired
|
|
25
|
+
*/
|
|
26
|
+
const isTokenExpired = (user: any): boolean => {
|
|
27
|
+
const auth0Data = user?.services?.auth0;
|
|
28
|
+
if (!auth0Data?.expiresAt) {
|
|
29
|
+
return true;
|
|
30
|
+
}
|
|
31
|
+
return new Date() >= new Date(auth0Data.expiresAt);
|
|
32
|
+
};
|
|
33
|
+
|
|
12
34
|
Meteor.methods({
|
|
13
35
|
/**
|
|
14
36
|
* Authenticate user with Auth0 ID token
|
|
@@ -78,7 +100,7 @@ Meteor.methods({
|
|
|
78
100
|
const auth0Id = tokenPayload.sub;
|
|
79
101
|
|
|
80
102
|
// Check if Auth0 account is already linked to another user
|
|
81
|
-
const existingUser = Meteor.users.
|
|
103
|
+
const existingUser = await Meteor.users.findOneAsync({
|
|
82
104
|
'services.auth0.id': auth0Id,
|
|
83
105
|
_id: { $ne: this.userId },
|
|
84
106
|
});
|
|
@@ -91,15 +113,22 @@ Meteor.methods({
|
|
|
91
113
|
}
|
|
92
114
|
|
|
93
115
|
// Link Auth0 account to current user
|
|
94
|
-
Meteor.users.
|
|
116
|
+
await Meteor.users.updateAsync(this.userId, {
|
|
95
117
|
$set: {
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
email: tokenPayload.email,
|
|
118
|
+
// Profile data (client-accessible)
|
|
119
|
+
'profile.auth0': {
|
|
99
120
|
name: tokenPayload.name,
|
|
100
121
|
picture: tokenPayload.picture,
|
|
122
|
+
email: tokenPayload.email,
|
|
101
123
|
email_verified: tokenPayload.email_verified,
|
|
102
124
|
},
|
|
125
|
+
// Token data (server-only)
|
|
126
|
+
'services.auth0': {
|
|
127
|
+
id: auth0Id,
|
|
128
|
+
accessToken: tokenPayload.aud,
|
|
129
|
+
expiresAt: new Date(tokenPayload.exp * 1000),
|
|
130
|
+
issuedAt: new Date(tokenPayload.iat * 1000),
|
|
131
|
+
},
|
|
103
132
|
},
|
|
104
133
|
});
|
|
105
134
|
|
|
@@ -122,12 +151,12 @@ Meteor.methods({
|
|
|
122
151
|
/**
|
|
123
152
|
* Unlink Auth0 account from current user
|
|
124
153
|
*/
|
|
125
|
-
'auth0.unlinkAccount'() {
|
|
154
|
+
async 'auth0.unlinkAccount'() {
|
|
126
155
|
if (!this.userId) {
|
|
127
156
|
throw new Meteor.Error('not-authorized', 'Must be logged in to unlink Auth0 account');
|
|
128
157
|
}
|
|
129
158
|
|
|
130
|
-
const user = Meteor.users.
|
|
159
|
+
const user = await Meteor.users.findOneAsync(this.userId);
|
|
131
160
|
|
|
132
161
|
if (!user?.services?.auth0) {
|
|
133
162
|
throw new Meteor.Error('no-auth0-account', 'No Auth0 account linked');
|
|
@@ -141,12 +170,102 @@ Meteor.methods({
|
|
|
141
170
|
);
|
|
142
171
|
}
|
|
143
172
|
|
|
144
|
-
Meteor.users.
|
|
173
|
+
await Meteor.users.updateAsync(this.userId, {
|
|
145
174
|
$unset: {
|
|
146
175
|
'services.auth0': '',
|
|
176
|
+
'profile.auth0': '',
|
|
147
177
|
},
|
|
148
178
|
});
|
|
149
179
|
|
|
150
180
|
return { success: true };
|
|
151
181
|
},
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Check if the current user's Auth0 token is valid
|
|
185
|
+
* Returns token expiry status
|
|
186
|
+
*/
|
|
187
|
+
async 'auth0.checkTokenValidity'() {
|
|
188
|
+
if (!this.userId) {
|
|
189
|
+
throw new Meteor.Error('not-authorized', 'Must be logged in');
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const user = await Meteor.users.findOneAsync(this.userId);
|
|
193
|
+
|
|
194
|
+
if (!user?.services?.auth0) {
|
|
195
|
+
throw new Meteor.Error('no-auth0-account', 'No Auth0 account linked');
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const expired = isTokenExpired(user);
|
|
199
|
+
|
|
200
|
+
return {
|
|
201
|
+
isValid: !expired,
|
|
202
|
+
expiresAt: user.services.auth0.expiresAt,
|
|
203
|
+
issuedAt: user.services.auth0.issuedAt,
|
|
204
|
+
};
|
|
205
|
+
},
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Refresh Auth0 token and update Meteor user session
|
|
209
|
+
* This method validates the new token and updates the user's token data
|
|
210
|
+
*/
|
|
211
|
+
async 'auth0.refreshToken'(idToken: string) {
|
|
212
|
+
if (!this.userId) {
|
|
213
|
+
throw new Meteor.Error('not-authorized', 'Must be logged in');
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
if (!idToken || typeof idToken !== 'string') {
|
|
217
|
+
throw new Meteor.Error('invalid-token', 'Invalid token provided');
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
try {
|
|
221
|
+
// Verify the new Auth0 token
|
|
222
|
+
const tokenPayload = await verifyAuth0Token(idToken);
|
|
223
|
+
|
|
224
|
+
// Get current user
|
|
225
|
+
const user = await Meteor.users.findOneAsync(this.userId);
|
|
226
|
+
|
|
227
|
+
if (!user?.services?.auth0) {
|
|
228
|
+
throw new Meteor.Error('no-auth0-account', 'No Auth0 account linked');
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// Verify the token is for the same Auth0 user
|
|
232
|
+
if (user.services.auth0.id !== tokenPayload.sub) {
|
|
233
|
+
throw new Meteor.Error(
|
|
234
|
+
'token-mismatch',
|
|
235
|
+
'Token does not match current user Auth0 account'
|
|
236
|
+
);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// Update token data in services.auth0 (server-only)
|
|
240
|
+
await Meteor.users.updateAsync(this.userId, {
|
|
241
|
+
$set: {
|
|
242
|
+
'services.auth0.accessToken': tokenPayload.aud,
|
|
243
|
+
'services.auth0.expiresAt': new Date(tokenPayload.exp * 1000),
|
|
244
|
+
'services.auth0.issuedAt': new Date(tokenPayload.iat * 1000),
|
|
245
|
+
// Also update profile data in case it changed
|
|
246
|
+
'profile.auth0.name': tokenPayload.name,
|
|
247
|
+
'profile.auth0.picture': tokenPayload.picture,
|
|
248
|
+
'profile.auth0.email': tokenPayload.email,
|
|
249
|
+
'profile.auth0.email_verified': tokenPayload.email_verified,
|
|
250
|
+
},
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
return {
|
|
254
|
+
success: true,
|
|
255
|
+
expiresAt: new Date(tokenPayload.exp * 1000),
|
|
256
|
+
};
|
|
257
|
+
} catch (error) {
|
|
258
|
+
console.error('Auth0 token refresh error:', error);
|
|
259
|
+
|
|
260
|
+
if (error instanceof Meteor.Error) {
|
|
261
|
+
throw error;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
throw new Meteor.Error(
|
|
265
|
+
'auth0-refresh-failed',
|
|
266
|
+
'Failed to refresh Auth0 token',
|
|
267
|
+
error.message
|
|
268
|
+
);
|
|
269
|
+
}
|
|
270
|
+
},
|
|
152
271
|
});
|
|
@@ -10,6 +10,15 @@ import jwksClient from 'jwks-rsa';
|
|
|
10
10
|
* and creates/updates Meteor user accounts based on Auth0 user data.
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
+
/**
|
|
14
|
+
* Normalize Auth0 domain by removing protocol and trailing slashes
|
|
15
|
+
*/
|
|
16
|
+
const normalizeDomain = (domain) => {
|
|
17
|
+
return domain
|
|
18
|
+
.replace(/^https?:\/\//, '') // Remove http:// or https://
|
|
19
|
+
.replace(/\/$/, ''); // Remove trailing slash
|
|
20
|
+
};
|
|
21
|
+
|
|
13
22
|
/**
|
|
14
23
|
* Create JWKS client for verifying Auth0 tokens
|
|
15
24
|
*/
|
|
@@ -20,8 +29,10 @@ const getJwksClient = () => {
|
|
|
20
29
|
throw new Meteor.Error('auth0-config-missing', 'Auth0 domain not configured');
|
|
21
30
|
}
|
|
22
31
|
|
|
32
|
+
const normalizedDomain = normalizeDomain(auth0Domain);
|
|
33
|
+
|
|
23
34
|
return jwksClient({
|
|
24
|
-
jwksUri: `https://${
|
|
35
|
+
jwksUri: `https://${normalizedDomain}/.well-known/jwks.json`,
|
|
25
36
|
cache: true,
|
|
26
37
|
cacheMaxAge: 86400000, // 24 hours
|
|
27
38
|
});
|
|
@@ -55,12 +66,14 @@ export const verifyAuth0Token = (token) => {
|
|
|
55
66
|
return;
|
|
56
67
|
}
|
|
57
68
|
|
|
69
|
+
const normalizedDomain = normalizeDomain(auth0Config.domain);
|
|
70
|
+
|
|
58
71
|
jwt.verify(
|
|
59
72
|
token,
|
|
60
73
|
getKey,
|
|
61
74
|
{
|
|
62
75
|
audience: auth0Config.clientId,
|
|
63
|
-
issuer: `https://${
|
|
76
|
+
issuer: `https://${normalizedDomain}/`,
|
|
64
77
|
algorithms: ['RS256'],
|
|
65
78
|
},
|
|
66
79
|
(err, decoded) => {
|
|
@@ -80,20 +93,29 @@ export const verifyAuth0Token = (token) => {
|
|
|
80
93
|
export const findOrCreateUserFromAuth0 = async (tokenPayload) => {
|
|
81
94
|
const auth0Id = tokenPayload.sub;
|
|
82
95
|
const email = tokenPayload.email;
|
|
96
|
+
const now = new Date();
|
|
83
97
|
|
|
84
98
|
// Try to find existing user by Auth0 ID
|
|
85
|
-
let user = Meteor.users.
|
|
99
|
+
let user = await Meteor.users.findOneAsync({
|
|
86
100
|
'services.auth0.id': auth0Id,
|
|
87
101
|
});
|
|
88
102
|
|
|
89
103
|
if (user) {
|
|
90
|
-
// Update user profile
|
|
91
|
-
Meteor.users.
|
|
104
|
+
// Update user profile and token data
|
|
105
|
+
await Meteor.users.updateAsync(user._id, {
|
|
92
106
|
$set: {
|
|
93
|
-
|
|
94
|
-
'
|
|
95
|
-
|
|
96
|
-
|
|
107
|
+
// Profile data (client-accessible)
|
|
108
|
+
'profile.auth0': {
|
|
109
|
+
name: tokenPayload.name,
|
|
110
|
+
picture: tokenPayload.picture,
|
|
111
|
+
email: email,
|
|
112
|
+
email_verified: tokenPayload.email_verified,
|
|
113
|
+
},
|
|
114
|
+
// Token data (server-only)
|
|
115
|
+
'services.auth0.id': auth0Id,
|
|
116
|
+
'services.auth0.accessToken': tokenPayload.aud,
|
|
117
|
+
'services.auth0.expiresAt': new Date(tokenPayload.exp * 1000),
|
|
118
|
+
'services.auth0.issuedAt': new Date(tokenPayload.iat * 1000),
|
|
97
119
|
},
|
|
98
120
|
});
|
|
99
121
|
return user._id;
|
|
@@ -101,21 +123,28 @@ export const findOrCreateUserFromAuth0 = async (tokenPayload) => {
|
|
|
101
123
|
|
|
102
124
|
// Try to find by email if email exists
|
|
103
125
|
if (email) {
|
|
104
|
-
user = Meteor.users.
|
|
126
|
+
user = await Meteor.users.findOneAsync({
|
|
105
127
|
'emails.address': email,
|
|
106
128
|
});
|
|
107
129
|
|
|
108
130
|
if (user) {
|
|
109
131
|
// Link Auth0 account to existing user
|
|
110
|
-
Meteor.users.
|
|
132
|
+
await Meteor.users.updateAsync(user._id, {
|
|
111
133
|
$set: {
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
email: email,
|
|
134
|
+
// Profile data (client-accessible)
|
|
135
|
+
'profile.auth0': {
|
|
115
136
|
name: tokenPayload.name,
|
|
116
137
|
picture: tokenPayload.picture,
|
|
138
|
+
email: email,
|
|
117
139
|
email_verified: tokenPayload.email_verified,
|
|
118
140
|
},
|
|
141
|
+
// Token data (server-only)
|
|
142
|
+
'services.auth0': {
|
|
143
|
+
id: auth0Id,
|
|
144
|
+
accessToken: tokenPayload.aud,
|
|
145
|
+
expiresAt: new Date(tokenPayload.exp * 1000),
|
|
146
|
+
issuedAt: new Date(tokenPayload.iat * 1000),
|
|
147
|
+
},
|
|
119
148
|
},
|
|
120
149
|
});
|
|
121
150
|
return user._id;
|
|
@@ -127,16 +156,23 @@ export const findOrCreateUserFromAuth0 = async (tokenPayload) => {
|
|
|
127
156
|
emails: email ? [{ address: email, verified: tokenPayload.email_verified || false }] : [],
|
|
128
157
|
profile: {
|
|
129
158
|
name: tokenPayload.name || tokenPayload.nickname || 'Auth0 User',
|
|
130
|
-
|
|
131
|
-
services: {
|
|
159
|
+
// Auth0 profile data (client-accessible)
|
|
132
160
|
auth0: {
|
|
133
|
-
id: auth0Id,
|
|
134
|
-
email: email,
|
|
135
161
|
name: tokenPayload.name,
|
|
136
162
|
picture: tokenPayload.picture,
|
|
163
|
+
email: email,
|
|
137
164
|
email_verified: tokenPayload.email_verified,
|
|
138
165
|
},
|
|
139
166
|
},
|
|
167
|
+
services: {
|
|
168
|
+
// Token data (server-only)
|
|
169
|
+
auth0: {
|
|
170
|
+
id: auth0Id,
|
|
171
|
+
accessToken: tokenPayload.aud,
|
|
172
|
+
expiresAt: new Date(tokenPayload.exp * 1000),
|
|
173
|
+
issuedAt: new Date(tokenPayload.iat * 1000),
|
|
174
|
+
},
|
|
175
|
+
},
|
|
140
176
|
});
|
|
141
177
|
|
|
142
178
|
return userId;
|
|
@@ -23,6 +23,15 @@ interface Auth0TokenPayload {
|
|
|
23
23
|
iat: number;
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
+
/**
|
|
27
|
+
* Normalize Auth0 domain by removing protocol and trailing slashes
|
|
28
|
+
*/
|
|
29
|
+
const normalizeDomain = (domain: string): string => {
|
|
30
|
+
return domain
|
|
31
|
+
.replace(/^https?:\/\//, '') // Remove http:// or https://
|
|
32
|
+
.replace(/\/$/, ''); // Remove trailing slash
|
|
33
|
+
};
|
|
34
|
+
|
|
26
35
|
/**
|
|
27
36
|
* Create JWKS client for verifying Auth0 tokens
|
|
28
37
|
*/
|
|
@@ -33,8 +42,10 @@ const getJwksClient = () => {
|
|
|
33
42
|
throw new Meteor.Error('auth0-config-missing', 'Auth0 domain not configured');
|
|
34
43
|
}
|
|
35
44
|
|
|
45
|
+
const normalizedDomain = normalizeDomain(auth0Domain);
|
|
46
|
+
|
|
36
47
|
return jwksClient({
|
|
37
|
-
jwksUri: `https://${
|
|
48
|
+
jwksUri: `https://${normalizedDomain}/.well-known/jwks.json`,
|
|
38
49
|
cache: true,
|
|
39
50
|
cacheMaxAge: 86400000, // 24 hours
|
|
40
51
|
});
|
|
@@ -68,12 +79,14 @@ export const verifyAuth0Token = (token: string): Promise<Auth0TokenPayload> => {
|
|
|
68
79
|
return;
|
|
69
80
|
}
|
|
70
81
|
|
|
82
|
+
const normalizedDomain = normalizeDomain(auth0Config.domain);
|
|
83
|
+
|
|
71
84
|
jwt.verify(
|
|
72
85
|
token,
|
|
73
86
|
getKey,
|
|
74
87
|
{
|
|
75
88
|
audience: auth0Config.clientId,
|
|
76
|
-
issuer: `https://${
|
|
89
|
+
issuer: `https://${normalizedDomain}/`,
|
|
77
90
|
algorithms: ['RS256'],
|
|
78
91
|
},
|
|
79
92
|
(err, decoded) => {
|
|
@@ -95,20 +108,29 @@ export const findOrCreateUserFromAuth0 = async (
|
|
|
95
108
|
): Promise<string> => {
|
|
96
109
|
const auth0Id = tokenPayload.sub;
|
|
97
110
|
const email = tokenPayload.email;
|
|
111
|
+
const now = new Date();
|
|
98
112
|
|
|
99
113
|
// Try to find existing user by Auth0 ID
|
|
100
|
-
let user = Meteor.users.
|
|
114
|
+
let user = await Meteor.users.findOneAsync({
|
|
101
115
|
'services.auth0.id': auth0Id,
|
|
102
116
|
});
|
|
103
117
|
|
|
104
118
|
if (user) {
|
|
105
|
-
// Update user profile
|
|
106
|
-
Meteor.users.
|
|
119
|
+
// Update user profile and token data
|
|
120
|
+
await Meteor.users.updateAsync(user._id, {
|
|
107
121
|
$set: {
|
|
108
|
-
|
|
109
|
-
'
|
|
110
|
-
|
|
111
|
-
|
|
122
|
+
// Profile data (client-accessible)
|
|
123
|
+
'profile.auth0': {
|
|
124
|
+
name: tokenPayload.name,
|
|
125
|
+
picture: tokenPayload.picture,
|
|
126
|
+
email: email,
|
|
127
|
+
email_verified: tokenPayload.email_verified,
|
|
128
|
+
},
|
|
129
|
+
// Token data (server-only)
|
|
130
|
+
'services.auth0.id': auth0Id,
|
|
131
|
+
'services.auth0.accessToken': tokenPayload.aud,
|
|
132
|
+
'services.auth0.expiresAt': new Date(tokenPayload.exp * 1000),
|
|
133
|
+
'services.auth0.issuedAt': new Date(tokenPayload.iat * 1000),
|
|
112
134
|
},
|
|
113
135
|
});
|
|
114
136
|
return user._id;
|
|
@@ -116,21 +138,28 @@ export const findOrCreateUserFromAuth0 = async (
|
|
|
116
138
|
|
|
117
139
|
// Try to find by email if email exists
|
|
118
140
|
if (email) {
|
|
119
|
-
user = Meteor.users.
|
|
141
|
+
user = await Meteor.users.findOneAsync({
|
|
120
142
|
'emails.address': email,
|
|
121
143
|
});
|
|
122
144
|
|
|
123
145
|
if (user) {
|
|
124
146
|
// Link Auth0 account to existing user
|
|
125
|
-
Meteor.users.
|
|
147
|
+
await Meteor.users.updateAsync(user._id, {
|
|
126
148
|
$set: {
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
email: email,
|
|
149
|
+
// Profile data (client-accessible)
|
|
150
|
+
'profile.auth0': {
|
|
130
151
|
name: tokenPayload.name,
|
|
131
152
|
picture: tokenPayload.picture,
|
|
153
|
+
email: email,
|
|
132
154
|
email_verified: tokenPayload.email_verified,
|
|
133
155
|
},
|
|
156
|
+
// Token data (server-only)
|
|
157
|
+
'services.auth0': {
|
|
158
|
+
id: auth0Id,
|
|
159
|
+
accessToken: tokenPayload.aud,
|
|
160
|
+
expiresAt: new Date(tokenPayload.exp * 1000),
|
|
161
|
+
issuedAt: new Date(tokenPayload.iat * 1000),
|
|
162
|
+
},
|
|
134
163
|
},
|
|
135
164
|
});
|
|
136
165
|
return user._id;
|
|
@@ -142,16 +171,23 @@ export const findOrCreateUserFromAuth0 = async (
|
|
|
142
171
|
emails: email ? [{ address: email, verified: tokenPayload.email_verified || false }] : [],
|
|
143
172
|
profile: {
|
|
144
173
|
name: tokenPayload.name || tokenPayload.nickname || 'Auth0 User',
|
|
145
|
-
|
|
146
|
-
services: {
|
|
174
|
+
// Auth0 profile data (client-accessible)
|
|
147
175
|
auth0: {
|
|
148
|
-
id: auth0Id,
|
|
149
|
-
email: email,
|
|
150
176
|
name: tokenPayload.name,
|
|
151
177
|
picture: tokenPayload.picture,
|
|
178
|
+
email: email,
|
|
152
179
|
email_verified: tokenPayload.email_verified,
|
|
153
180
|
},
|
|
154
181
|
},
|
|
182
|
+
services: {
|
|
183
|
+
// Token data (server-only)
|
|
184
|
+
auth0: {
|
|
185
|
+
id: auth0Id,
|
|
186
|
+
accessToken: tokenPayload.aud,
|
|
187
|
+
expiresAt: new Date(tokenPayload.exp * 1000),
|
|
188
|
+
issuedAt: new Date(tokenPayload.iat * 1000),
|
|
189
|
+
},
|
|
190
|
+
},
|
|
155
191
|
});
|
|
156
192
|
|
|
157
193
|
return userId;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@maka/maka-cli",
|
|
3
|
-
"version": "5.1.
|
|
3
|
+
"version": "5.1.47",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"summary": "A command line tool for scaffolding Meteor 2.x applications using either React.",
|
|
6
6
|
"description": "A command line tool for scaffolding Meteor 2.x applications using React.",
|