@maka/maka-cli 5.1.45 → 5.1.46
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/.claude/settings.local.json +3 -1
- package/bundle/typescript/package.json +1 -1
- package/bundle/typescript/src/commands/ai/mcp.sub.cmd.js +118 -5
- package/bundle/typescript/src/commands/ai/mcp.sub.cmd.js.map +1 -1
- package/bundle/typescript/src/commands/create.js +2 -2
- package/bundle/typescript/src/commands/create.js.map +1 -1
- package/bundle/typescript/src/commands/run/run.command.js +0 -5
- package/bundle/typescript/src/commands/run/run.command.js.map +1 -1
- package/bundle/typescript/src/generators/kits/auth/_index.d.ts +1 -0
- package/bundle/typescript/src/generators/kits/auth/_index.js +1 -0
- package/bundle/typescript/src/generators/kits/auth/_index.js.map +1 -1
- package/bundle/typescript/src/generators/kits/auth/auth0-meteor.auth.gen.d.ts +1 -0
- package/bundle/typescript/src/generators/kits/auth/auth0-meteor.auth.gen.js +704 -0
- package/bundle/typescript/src/generators/kits/auth/auth0-meteor.auth.gen.js.map +1 -0
- package/bundle/typescript/src/generators/kits/auth/basic-meteor.auth.gen.js +2 -2
- package/bundle/typescript/src/generators/kits/auth/basic-meteor.auth.gen.js.map +1 -1
- package/bundle/typescript/src/generators/kits/mui/_index.d.ts +1 -0
- package/bundle/typescript/src/generators/kits/mui/_index.js +1 -0
- package/bundle/typescript/src/generators/kits/mui/_index.js.map +1 -1
- package/bundle/typescript/src/generators/kits/mui/auth0.mui.gen.d.ts +2 -0
- package/bundle/typescript/src/generators/kits/mui/auth0.mui.gen.js +158 -0
- package/bundle/typescript/src/generators/kits/mui/auth0.mui.gen.js.map +1 -0
- package/bundle/typescript/src/generators/publish.js +2 -6
- package/bundle/typescript/src/generators/publish.js.map +1 -1
- package/bundle/typescript/src/templates/react/kit/auth/service/auth0-client.js.jsx +141 -0
- package/bundle/typescript/src/templates/react/kit/auth/service/auth0-client.js.tsx +141 -0
- package/bundle/typescript/src/templates/react/kit/auth/service/auth0-config.js.jsx +46 -0
- package/bundle/typescript/src/templates/react/kit/auth/service/auth0-config.js.tsx +54 -0
- package/bundle/typescript/src/templates/react/kit/auth/service/auth0-methods.js.jsx +152 -0
- package/bundle/typescript/src/templates/react/kit/auth/service/auth0-methods.js.tsx +152 -0
- package/bundle/typescript/src/templates/react/kit/auth/service/auth0-validator.js.jsx +143 -0
- package/bundle/typescript/src/templates/react/kit/auth/service/auth0-validator.js.tsx +158 -0
- package/bundle/typescript/src/templates/react/kit/mui/auth0/auth-callback.js.jsx +71 -0
- package/bundle/typescript/src/templates/react/kit/mui/auth0/auth-callback.js.tsx +71 -0
- package/bundle/typescript/src/templates/react/kit/mui/auth0/auth0-account-settings.js.jsx +123 -0
- package/bundle/typescript/src/templates/react/kit/mui/auth0/auth0-account-settings.js.tsx +123 -0
- package/bundle/typescript/src/templates/react/kit/mui/auth0/auth0-login-button.js.jsx +56 -0
- package/bundle/typescript/src/templates/react/kit/mui/auth0/auth0-login-button.js.tsx +74 -0
- package/bundle/typescript/src/tools/scaffold/scaffold.class.d.ts +2 -1
- package/bundle/typescript/src/tools/scaffold/scaffold.class.js +12 -6
- package/bundle/typescript/src/tools/scaffold/scaffold.class.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auth0 Configuration
|
|
3
|
+
*
|
|
4
|
+
* This file contains the Auth0 configuration settings.
|
|
5
|
+
* Set your Auth0 credentials in Meteor settings or environment variables.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export interface Auth0Config {
|
|
9
|
+
domain: string;
|
|
10
|
+
clientId: string;
|
|
11
|
+
audience?: string;
|
|
12
|
+
redirectUri: string;
|
|
13
|
+
scope: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Get Auth0 configuration from Meteor settings or environment variables
|
|
18
|
+
*/
|
|
19
|
+
export const getAuth0Config = (): Auth0Config => {
|
|
20
|
+
if (Meteor.isClient) {
|
|
21
|
+
const settings = Meteor.settings.public?.auth0;
|
|
22
|
+
|
|
23
|
+
if (!settings?.domain || !settings?.clientId) {
|
|
24
|
+
throw new Error(
|
|
25
|
+
'Auth0 configuration missing. Please add auth0 settings to your Meteor settings.json file.'
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return {
|
|
30
|
+
domain: settings.domain,
|
|
31
|
+
clientId: settings.clientId,
|
|
32
|
+
audience: settings.audience || `https://${settings.domain}/api/v2/`,
|
|
33
|
+
redirectUri: settings.redirectUri || window.location.origin + '/auth/callback',
|
|
34
|
+
scope: settings.scope || 'openid profile email',
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Server-side configuration
|
|
39
|
+
const settings = Meteor.settings?.auth0;
|
|
40
|
+
|
|
41
|
+
if (!settings?.domain || !settings?.clientId) {
|
|
42
|
+
throw new Error(
|
|
43
|
+
'Auth0 configuration missing. Please add auth0 settings to your Meteor settings.json file.'
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return {
|
|
48
|
+
domain: settings.domain,
|
|
49
|
+
clientId: settings.clientId,
|
|
50
|
+
audience: settings.audience || `https://${settings.domain}/api/v2/`,
|
|
51
|
+
redirectUri: settings.redirectUri || Meteor.absoluteUrl('auth/callback'),
|
|
52
|
+
scope: settings.scope || 'openid profile email',
|
|
53
|
+
};
|
|
54
|
+
};
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import { Meteor } from 'meteor/meteor';
|
|
2
|
+
import { Accounts } from 'meteor/accounts-base';
|
|
3
|
+
import { verifyAuth0Token, findOrCreateUserFromAuth0 } from '../auth0-validator';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Meteor Methods for Auth0 Authentication
|
|
7
|
+
*
|
|
8
|
+
* These methods handle the server-side Auth0 authentication flow,
|
|
9
|
+
* including token verification and user account creation/linking.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
Meteor.methods({
|
|
13
|
+
/**
|
|
14
|
+
* Authenticate user with Auth0 ID token
|
|
15
|
+
*
|
|
16
|
+
* @param idToken - The Auth0 ID token received from the client
|
|
17
|
+
* @returns Object containing userId and login token
|
|
18
|
+
*/
|
|
19
|
+
async 'auth0.login'(idToken) {
|
|
20
|
+
// Validate input
|
|
21
|
+
if (!idToken || typeof idToken !== 'string') {
|
|
22
|
+
throw new Meteor.Error('invalid-token', 'Invalid token provided');
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
try {
|
|
26
|
+
// Verify the Auth0 token
|
|
27
|
+
const tokenPayload = await verifyAuth0Token(idToken);
|
|
28
|
+
|
|
29
|
+
// Find or create user from Auth0 data
|
|
30
|
+
const userId = await findOrCreateUserFromAuth0(tokenPayload);
|
|
31
|
+
|
|
32
|
+
// Generate Meteor login token
|
|
33
|
+
const stampedLoginToken = Accounts._generateStampedLoginToken();
|
|
34
|
+
const loginToken = stampedLoginToken.token;
|
|
35
|
+
|
|
36
|
+
// Store login token for the user
|
|
37
|
+
Accounts._insertLoginToken(userId, stampedLoginToken);
|
|
38
|
+
|
|
39
|
+
// Return userId and token for client-side login
|
|
40
|
+
return {
|
|
41
|
+
userId,
|
|
42
|
+
token: loginToken,
|
|
43
|
+
tokenExpires: stampedLoginToken.when,
|
|
44
|
+
};
|
|
45
|
+
} catch (error) {
|
|
46
|
+
console.error('Auth0 login error:', error);
|
|
47
|
+
|
|
48
|
+
if (error instanceof Meteor.Error) {
|
|
49
|
+
throw error;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
throw new Meteor.Error(
|
|
53
|
+
'auth0-login-failed',
|
|
54
|
+
'Failed to authenticate with Auth0',
|
|
55
|
+
error.message
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Link Auth0 account to existing Meteor user
|
|
62
|
+
*
|
|
63
|
+
* @param idToken - The Auth0 ID token
|
|
64
|
+
*/
|
|
65
|
+
async 'auth0.linkAccount'(idToken) {
|
|
66
|
+
// Must be logged in to link account
|
|
67
|
+
if (!this.userId) {
|
|
68
|
+
throw new Meteor.Error('not-authorized', 'Must be logged in to link Auth0 account');
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (!idToken || typeof idToken !== 'string') {
|
|
72
|
+
throw new Meteor.Error('invalid-token', 'Invalid token provided');
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
try {
|
|
76
|
+
// Verify the Auth0 token
|
|
77
|
+
const tokenPayload = await verifyAuth0Token(idToken);
|
|
78
|
+
const auth0Id = tokenPayload.sub;
|
|
79
|
+
|
|
80
|
+
// Check if Auth0 account is already linked to another user
|
|
81
|
+
const existingUser = Meteor.users.findOne({
|
|
82
|
+
'services.auth0.id': auth0Id,
|
|
83
|
+
_id: { $ne: this.userId },
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
if (existingUser) {
|
|
87
|
+
throw new Meteor.Error(
|
|
88
|
+
'auth0-already-linked',
|
|
89
|
+
'This Auth0 account is already linked to another user'
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Link Auth0 account to current user
|
|
94
|
+
Meteor.users.update(this.userId, {
|
|
95
|
+
$set: {
|
|
96
|
+
'services.auth0': {
|
|
97
|
+
id: auth0Id,
|
|
98
|
+
email: tokenPayload.email,
|
|
99
|
+
name: tokenPayload.name,
|
|
100
|
+
picture: tokenPayload.picture,
|
|
101
|
+
email_verified: tokenPayload.email_verified,
|
|
102
|
+
},
|
|
103
|
+
},
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
return { success: true };
|
|
107
|
+
} catch (error) {
|
|
108
|
+
console.error('Auth0 link account error:', error);
|
|
109
|
+
|
|
110
|
+
if (error instanceof Meteor.Error) {
|
|
111
|
+
throw error;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
throw new Meteor.Error(
|
|
115
|
+
'auth0-link-failed',
|
|
116
|
+
'Failed to link Auth0 account',
|
|
117
|
+
error.message
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
},
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Unlink Auth0 account from current user
|
|
124
|
+
*/
|
|
125
|
+
'auth0.unlinkAccount'() {
|
|
126
|
+
if (!this.userId) {
|
|
127
|
+
throw new Meteor.Error('not-authorized', 'Must be logged in to unlink Auth0 account');
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const user = Meteor.users.findOne(this.userId);
|
|
131
|
+
|
|
132
|
+
if (!user?.services?.auth0) {
|
|
133
|
+
throw new Meteor.Error('no-auth0-account', 'No Auth0 account linked');
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Make sure user has another way to login (password or other service)
|
|
137
|
+
if (!user.services.password && Object.keys(user.services).length === 1) {
|
|
138
|
+
throw new Meteor.Error(
|
|
139
|
+
'last-login-method',
|
|
140
|
+
'Cannot unlink Auth0 - it is your only login method. Add a password first.'
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
Meteor.users.update(this.userId, {
|
|
145
|
+
$unset: {
|
|
146
|
+
'services.auth0': '',
|
|
147
|
+
},
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
return { success: true };
|
|
151
|
+
},
|
|
152
|
+
});
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import { Meteor } from 'meteor/meteor';
|
|
2
|
+
import { Accounts } from 'meteor/accounts-base';
|
|
3
|
+
import { verifyAuth0Token, findOrCreateUserFromAuth0 } from '../auth0-validator';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Meteor Methods for Auth0 Authentication
|
|
7
|
+
*
|
|
8
|
+
* These methods handle the server-side Auth0 authentication flow,
|
|
9
|
+
* including token verification and user account creation/linking.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
Meteor.methods({
|
|
13
|
+
/**
|
|
14
|
+
* Authenticate user with Auth0 ID token
|
|
15
|
+
*
|
|
16
|
+
* @param idToken - The Auth0 ID token received from the client
|
|
17
|
+
* @returns Object containing userId and login token
|
|
18
|
+
*/
|
|
19
|
+
async 'auth0.login'(idToken: string) {
|
|
20
|
+
// Validate input
|
|
21
|
+
if (!idToken || typeof idToken !== 'string') {
|
|
22
|
+
throw new Meteor.Error('invalid-token', 'Invalid token provided');
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
try {
|
|
26
|
+
// Verify the Auth0 token
|
|
27
|
+
const tokenPayload = await verifyAuth0Token(idToken);
|
|
28
|
+
|
|
29
|
+
// Find or create user from Auth0 data
|
|
30
|
+
const userId = await findOrCreateUserFromAuth0(tokenPayload);
|
|
31
|
+
|
|
32
|
+
// Generate Meteor login token
|
|
33
|
+
const stampedLoginToken = Accounts._generateStampedLoginToken();
|
|
34
|
+
const loginToken = stampedLoginToken.token;
|
|
35
|
+
|
|
36
|
+
// Store login token for the user
|
|
37
|
+
Accounts._insertLoginToken(userId, stampedLoginToken);
|
|
38
|
+
|
|
39
|
+
// Return userId and token for client-side login
|
|
40
|
+
return {
|
|
41
|
+
userId,
|
|
42
|
+
token: loginToken,
|
|
43
|
+
tokenExpires: stampedLoginToken.when,
|
|
44
|
+
};
|
|
45
|
+
} catch (error) {
|
|
46
|
+
console.error('Auth0 login error:', error);
|
|
47
|
+
|
|
48
|
+
if (error instanceof Meteor.Error) {
|
|
49
|
+
throw error;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
throw new Meteor.Error(
|
|
53
|
+
'auth0-login-failed',
|
|
54
|
+
'Failed to authenticate with Auth0',
|
|
55
|
+
error.message
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Link Auth0 account to existing Meteor user
|
|
62
|
+
*
|
|
63
|
+
* @param idToken - The Auth0 ID token
|
|
64
|
+
*/
|
|
65
|
+
async 'auth0.linkAccount'(idToken: string) {
|
|
66
|
+
// Must be logged in to link account
|
|
67
|
+
if (!this.userId) {
|
|
68
|
+
throw new Meteor.Error('not-authorized', 'Must be logged in to link Auth0 account');
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (!idToken || typeof idToken !== 'string') {
|
|
72
|
+
throw new Meteor.Error('invalid-token', 'Invalid token provided');
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
try {
|
|
76
|
+
// Verify the Auth0 token
|
|
77
|
+
const tokenPayload = await verifyAuth0Token(idToken);
|
|
78
|
+
const auth0Id = tokenPayload.sub;
|
|
79
|
+
|
|
80
|
+
// Check if Auth0 account is already linked to another user
|
|
81
|
+
const existingUser = Meteor.users.findOne({
|
|
82
|
+
'services.auth0.id': auth0Id,
|
|
83
|
+
_id: { $ne: this.userId },
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
if (existingUser) {
|
|
87
|
+
throw new Meteor.Error(
|
|
88
|
+
'auth0-already-linked',
|
|
89
|
+
'This Auth0 account is already linked to another user'
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Link Auth0 account to current user
|
|
94
|
+
Meteor.users.update(this.userId, {
|
|
95
|
+
$set: {
|
|
96
|
+
'services.auth0': {
|
|
97
|
+
id: auth0Id,
|
|
98
|
+
email: tokenPayload.email,
|
|
99
|
+
name: tokenPayload.name,
|
|
100
|
+
picture: tokenPayload.picture,
|
|
101
|
+
email_verified: tokenPayload.email_verified,
|
|
102
|
+
},
|
|
103
|
+
},
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
return { success: true };
|
|
107
|
+
} catch (error) {
|
|
108
|
+
console.error('Auth0 link account error:', error);
|
|
109
|
+
|
|
110
|
+
if (error instanceof Meteor.Error) {
|
|
111
|
+
throw error;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
throw new Meteor.Error(
|
|
115
|
+
'auth0-link-failed',
|
|
116
|
+
'Failed to link Auth0 account',
|
|
117
|
+
error.message
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
},
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Unlink Auth0 account from current user
|
|
124
|
+
*/
|
|
125
|
+
'auth0.unlinkAccount'() {
|
|
126
|
+
if (!this.userId) {
|
|
127
|
+
throw new Meteor.Error('not-authorized', 'Must be logged in to unlink Auth0 account');
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const user = Meteor.users.findOne(this.userId);
|
|
131
|
+
|
|
132
|
+
if (!user?.services?.auth0) {
|
|
133
|
+
throw new Meteor.Error('no-auth0-account', 'No Auth0 account linked');
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Make sure user has another way to login (password or other service)
|
|
137
|
+
if (!user.services.password && Object.keys(user.services).length === 1) {
|
|
138
|
+
throw new Meteor.Error(
|
|
139
|
+
'last-login-method',
|
|
140
|
+
'Cannot unlink Auth0 - it is your only login method. Add a password first.'
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
Meteor.users.update(this.userId, {
|
|
145
|
+
$unset: {
|
|
146
|
+
'services.auth0': '',
|
|
147
|
+
},
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
return { success: true };
|
|
151
|
+
},
|
|
152
|
+
});
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { Meteor } from 'meteor/meteor';
|
|
2
|
+
import { Accounts } from 'meteor/accounts-base';
|
|
3
|
+
import jwt from 'jsonwebtoken';
|
|
4
|
+
import jwksClient from 'jwks-rsa';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Auth0 JWT Token Validator
|
|
8
|
+
*
|
|
9
|
+
* This module handles server-side validation of Auth0 JWT tokens
|
|
10
|
+
* and creates/updates Meteor user accounts based on Auth0 user data.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Create JWKS client for verifying Auth0 tokens
|
|
15
|
+
*/
|
|
16
|
+
const getJwksClient = () => {
|
|
17
|
+
const auth0Domain = Meteor.settings?.auth0?.domain;
|
|
18
|
+
|
|
19
|
+
if (!auth0Domain) {
|
|
20
|
+
throw new Meteor.Error('auth0-config-missing', 'Auth0 domain not configured');
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return jwksClient({
|
|
24
|
+
jwksUri: `https://${auth0Domain}/.well-known/jwks.json`,
|
|
25
|
+
cache: true,
|
|
26
|
+
cacheMaxAge: 86400000, // 24 hours
|
|
27
|
+
});
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Get signing key for JWT verification
|
|
32
|
+
*/
|
|
33
|
+
const getKey = (header, callback) => {
|
|
34
|
+
const client = getJwksClient();
|
|
35
|
+
|
|
36
|
+
client.getSigningKey(header.kid, (err, key) => {
|
|
37
|
+
if (err) {
|
|
38
|
+
callback(err);
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
const signingKey = key?.getPublicKey();
|
|
42
|
+
callback(null, signingKey);
|
|
43
|
+
});
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Verify Auth0 JWT token
|
|
48
|
+
*/
|
|
49
|
+
export const verifyAuth0Token = (token) => {
|
|
50
|
+
return new Promise((resolve, reject) => {
|
|
51
|
+
const auth0Config = Meteor.settings?.auth0;
|
|
52
|
+
|
|
53
|
+
if (!auth0Config?.domain || !auth0Config?.clientId) {
|
|
54
|
+
reject(new Meteor.Error('auth0-config-missing', 'Auth0 configuration missing'));
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
jwt.verify(
|
|
59
|
+
token,
|
|
60
|
+
getKey,
|
|
61
|
+
{
|
|
62
|
+
audience: auth0Config.clientId,
|
|
63
|
+
issuer: `https://${auth0Config.domain}/`,
|
|
64
|
+
algorithms: ['RS256'],
|
|
65
|
+
},
|
|
66
|
+
(err, decoded) => {
|
|
67
|
+
if (err) {
|
|
68
|
+
reject(new Meteor.Error('auth0-invalid-token', 'Invalid Auth0 token', err));
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
resolve(decoded);
|
|
72
|
+
}
|
|
73
|
+
);
|
|
74
|
+
});
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Find or create a Meteor user from Auth0 data
|
|
79
|
+
*/
|
|
80
|
+
export const findOrCreateUserFromAuth0 = async (tokenPayload) => {
|
|
81
|
+
const auth0Id = tokenPayload.sub;
|
|
82
|
+
const email = tokenPayload.email;
|
|
83
|
+
|
|
84
|
+
// Try to find existing user by Auth0 ID
|
|
85
|
+
let user = Meteor.users.findOne({
|
|
86
|
+
'services.auth0.id': auth0Id,
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
if (user) {
|
|
90
|
+
// Update user profile if needed
|
|
91
|
+
Meteor.users.update(user._id, {
|
|
92
|
+
$set: {
|
|
93
|
+
'services.auth0.email': email,
|
|
94
|
+
'services.auth0.name': tokenPayload.name,
|
|
95
|
+
'services.auth0.picture': tokenPayload.picture,
|
|
96
|
+
'services.auth0.email_verified': tokenPayload.email_verified,
|
|
97
|
+
},
|
|
98
|
+
});
|
|
99
|
+
return user._id;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Try to find by email if email exists
|
|
103
|
+
if (email) {
|
|
104
|
+
user = Meteor.users.findOne({
|
|
105
|
+
'emails.address': email,
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
if (user) {
|
|
109
|
+
// Link Auth0 account to existing user
|
|
110
|
+
Meteor.users.update(user._id, {
|
|
111
|
+
$set: {
|
|
112
|
+
'services.auth0': {
|
|
113
|
+
id: auth0Id,
|
|
114
|
+
email: email,
|
|
115
|
+
name: tokenPayload.name,
|
|
116
|
+
picture: tokenPayload.picture,
|
|
117
|
+
email_verified: tokenPayload.email_verified,
|
|
118
|
+
},
|
|
119
|
+
},
|
|
120
|
+
});
|
|
121
|
+
return user._id;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Create new user
|
|
126
|
+
const userId = Accounts.insertUserDoc({}, {
|
|
127
|
+
emails: email ? [{ address: email, verified: tokenPayload.email_verified || false }] : [],
|
|
128
|
+
profile: {
|
|
129
|
+
name: tokenPayload.name || tokenPayload.nickname || 'Auth0 User',
|
|
130
|
+
},
|
|
131
|
+
services: {
|
|
132
|
+
auth0: {
|
|
133
|
+
id: auth0Id,
|
|
134
|
+
email: email,
|
|
135
|
+
name: tokenPayload.name,
|
|
136
|
+
picture: tokenPayload.picture,
|
|
137
|
+
email_verified: tokenPayload.email_verified,
|
|
138
|
+
},
|
|
139
|
+
},
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
return userId;
|
|
143
|
+
};
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import { Meteor } from 'meteor/meteor';
|
|
2
|
+
import { Accounts } from 'meteor/accounts-base';
|
|
3
|
+
import jwt from 'jsonwebtoken';
|
|
4
|
+
import jwksClient from 'jwks-rsa';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Auth0 JWT Token Validator
|
|
8
|
+
*
|
|
9
|
+
* This module handles server-side validation of Auth0 JWT tokens
|
|
10
|
+
* and creates/updates Meteor user accounts based on Auth0 user data.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
interface Auth0TokenPayload {
|
|
14
|
+
sub: string; // Auth0 user ID
|
|
15
|
+
email?: string;
|
|
16
|
+
email_verified?: boolean;
|
|
17
|
+
name?: string;
|
|
18
|
+
nickname?: string;
|
|
19
|
+
picture?: string;
|
|
20
|
+
aud: string | string[];
|
|
21
|
+
iss: string;
|
|
22
|
+
exp: number;
|
|
23
|
+
iat: number;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Create JWKS client for verifying Auth0 tokens
|
|
28
|
+
*/
|
|
29
|
+
const getJwksClient = () => {
|
|
30
|
+
const auth0Domain = Meteor.settings?.auth0?.domain;
|
|
31
|
+
|
|
32
|
+
if (!auth0Domain) {
|
|
33
|
+
throw new Meteor.Error('auth0-config-missing', 'Auth0 domain not configured');
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return jwksClient({
|
|
37
|
+
jwksUri: `https://${auth0Domain}/.well-known/jwks.json`,
|
|
38
|
+
cache: true,
|
|
39
|
+
cacheMaxAge: 86400000, // 24 hours
|
|
40
|
+
});
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Get signing key for JWT verification
|
|
45
|
+
*/
|
|
46
|
+
const getKey = (header: any, callback: any) => {
|
|
47
|
+
const client = getJwksClient();
|
|
48
|
+
|
|
49
|
+
client.getSigningKey(header.kid, (err, key) => {
|
|
50
|
+
if (err) {
|
|
51
|
+
callback(err);
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
const signingKey = key?.getPublicKey();
|
|
55
|
+
callback(null, signingKey);
|
|
56
|
+
});
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Verify Auth0 JWT token
|
|
61
|
+
*/
|
|
62
|
+
export const verifyAuth0Token = (token: string): Promise<Auth0TokenPayload> => {
|
|
63
|
+
return new Promise((resolve, reject) => {
|
|
64
|
+
const auth0Config = Meteor.settings?.auth0;
|
|
65
|
+
|
|
66
|
+
if (!auth0Config?.domain || !auth0Config?.clientId) {
|
|
67
|
+
reject(new Meteor.Error('auth0-config-missing', 'Auth0 configuration missing'));
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
jwt.verify(
|
|
72
|
+
token,
|
|
73
|
+
getKey,
|
|
74
|
+
{
|
|
75
|
+
audience: auth0Config.clientId,
|
|
76
|
+
issuer: `https://${auth0Config.domain}/`,
|
|
77
|
+
algorithms: ['RS256'],
|
|
78
|
+
},
|
|
79
|
+
(err, decoded) => {
|
|
80
|
+
if (err) {
|
|
81
|
+
reject(new Meteor.Error('auth0-invalid-token', 'Invalid Auth0 token', err));
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
resolve(decoded as Auth0TokenPayload);
|
|
85
|
+
}
|
|
86
|
+
);
|
|
87
|
+
});
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Find or create a Meteor user from Auth0 data
|
|
92
|
+
*/
|
|
93
|
+
export const findOrCreateUserFromAuth0 = async (
|
|
94
|
+
tokenPayload: Auth0TokenPayload
|
|
95
|
+
): Promise<string> => {
|
|
96
|
+
const auth0Id = tokenPayload.sub;
|
|
97
|
+
const email = tokenPayload.email;
|
|
98
|
+
|
|
99
|
+
// Try to find existing user by Auth0 ID
|
|
100
|
+
let user = Meteor.users.findOne({
|
|
101
|
+
'services.auth0.id': auth0Id,
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
if (user) {
|
|
105
|
+
// Update user profile if needed
|
|
106
|
+
Meteor.users.update(user._id, {
|
|
107
|
+
$set: {
|
|
108
|
+
'services.auth0.email': email,
|
|
109
|
+
'services.auth0.name': tokenPayload.name,
|
|
110
|
+
'services.auth0.picture': tokenPayload.picture,
|
|
111
|
+
'services.auth0.email_verified': tokenPayload.email_verified,
|
|
112
|
+
},
|
|
113
|
+
});
|
|
114
|
+
return user._id;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Try to find by email if email exists
|
|
118
|
+
if (email) {
|
|
119
|
+
user = Meteor.users.findOne({
|
|
120
|
+
'emails.address': email,
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
if (user) {
|
|
124
|
+
// Link Auth0 account to existing user
|
|
125
|
+
Meteor.users.update(user._id, {
|
|
126
|
+
$set: {
|
|
127
|
+
'services.auth0': {
|
|
128
|
+
id: auth0Id,
|
|
129
|
+
email: email,
|
|
130
|
+
name: tokenPayload.name,
|
|
131
|
+
picture: tokenPayload.picture,
|
|
132
|
+
email_verified: tokenPayload.email_verified,
|
|
133
|
+
},
|
|
134
|
+
},
|
|
135
|
+
});
|
|
136
|
+
return user._id;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Create new user
|
|
141
|
+
const userId = Accounts.insertUserDoc({}, {
|
|
142
|
+
emails: email ? [{ address: email, verified: tokenPayload.email_verified || false }] : [],
|
|
143
|
+
profile: {
|
|
144
|
+
name: tokenPayload.name || tokenPayload.nickname || 'Auth0 User',
|
|
145
|
+
},
|
|
146
|
+
services: {
|
|
147
|
+
auth0: {
|
|
148
|
+
id: auth0Id,
|
|
149
|
+
email: email,
|
|
150
|
+
name: tokenPayload.name,
|
|
151
|
+
picture: tokenPayload.picture,
|
|
152
|
+
email_verified: tokenPayload.email_verified,
|
|
153
|
+
},
|
|
154
|
+
},
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
return userId;
|
|
158
|
+
};
|