@maka/maka-cli 5.1.45 → 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.
Files changed (42) hide show
  1. package/.claude/settings.local.json +3 -1
  2. package/bundle/typescript/package.json +1 -1
  3. package/bundle/typescript/src/commands/ai/mcp.sub.cmd.js +118 -5
  4. package/bundle/typescript/src/commands/ai/mcp.sub.cmd.js.map +1 -1
  5. package/bundle/typescript/src/commands/create.js +2 -2
  6. package/bundle/typescript/src/commands/create.js.map +1 -1
  7. package/bundle/typescript/src/commands/run/run.command.js +0 -5
  8. package/bundle/typescript/src/commands/run/run.command.js.map +1 -1
  9. package/bundle/typescript/src/generators/kits/auth/_index.d.ts +1 -0
  10. package/bundle/typescript/src/generators/kits/auth/_index.js +1 -0
  11. package/bundle/typescript/src/generators/kits/auth/_index.js.map +1 -1
  12. package/bundle/typescript/src/generators/kits/auth/auth0-meteor.auth.gen.d.ts +1 -0
  13. package/bundle/typescript/src/generators/kits/auth/auth0-meteor.auth.gen.js +745 -0
  14. package/bundle/typescript/src/generators/kits/auth/auth0-meteor.auth.gen.js.map +1 -0
  15. package/bundle/typescript/src/generators/kits/auth/basic-meteor.auth.gen.js +2 -2
  16. package/bundle/typescript/src/generators/kits/auth/basic-meteor.auth.gen.js.map +1 -1
  17. package/bundle/typescript/src/generators/kits/mui/_index.d.ts +1 -0
  18. package/bundle/typescript/src/generators/kits/mui/_index.js +1 -0
  19. package/bundle/typescript/src/generators/kits/mui/_index.js.map +1 -1
  20. package/bundle/typescript/src/generators/kits/mui/auth0.mui.gen.d.ts +2 -0
  21. package/bundle/typescript/src/generators/kits/mui/auth0.mui.gen.js +158 -0
  22. package/bundle/typescript/src/generators/kits/mui/auth0.mui.gen.js.map +1 -0
  23. package/bundle/typescript/src/generators/publish.js +2 -6
  24. package/bundle/typescript/src/generators/publish.js.map +1 -1
  25. package/bundle/typescript/src/templates/react/kit/auth/service/auth0-client.js.jsx +163 -0
  26. package/bundle/typescript/src/templates/react/kit/auth/service/auth0-client.js.tsx +163 -0
  27. package/bundle/typescript/src/templates/react/kit/auth/service/auth0-config.js.jsx +59 -0
  28. package/bundle/typescript/src/templates/react/kit/auth/service/auth0-config.js.tsx +67 -0
  29. package/bundle/typescript/src/templates/react/kit/auth/service/auth0-methods.js.jsx +271 -0
  30. package/bundle/typescript/src/templates/react/kit/auth/service/auth0-methods.js.tsx +271 -0
  31. package/bundle/typescript/src/templates/react/kit/auth/service/auth0-validator.js.jsx +179 -0
  32. package/bundle/typescript/src/templates/react/kit/auth/service/auth0-validator.js.tsx +194 -0
  33. package/bundle/typescript/src/templates/react/kit/mui/auth0/auth-callback.js.jsx +71 -0
  34. package/bundle/typescript/src/templates/react/kit/mui/auth0/auth-callback.js.tsx +71 -0
  35. package/bundle/typescript/src/templates/react/kit/mui/auth0/auth0-account-settings.js.jsx +123 -0
  36. package/bundle/typescript/src/templates/react/kit/mui/auth0/auth0-account-settings.js.tsx +123 -0
  37. package/bundle/typescript/src/templates/react/kit/mui/auth0/auth0-login-button.js.jsx +56 -0
  38. package/bundle/typescript/src/templates/react/kit/mui/auth0/auth0-login-button.js.tsx +74 -0
  39. package/bundle/typescript/src/tools/scaffold/scaffold.class.d.ts +2 -1
  40. package/bundle/typescript/src/tools/scaffold/scaffold.class.js +12 -6
  41. package/bundle/typescript/src/tools/scaffold/scaffold.class.js.map +1 -1
  42. package/package.json +1 -1
@@ -0,0 +1,179 @@
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
+ * 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
+
22
+ /**
23
+ * Create JWKS client for verifying Auth0 tokens
24
+ */
25
+ const getJwksClient = () => {
26
+ const auth0Domain = Meteor.settings?.auth0?.domain;
27
+
28
+ if (!auth0Domain) {
29
+ throw new Meteor.Error('auth0-config-missing', 'Auth0 domain not configured');
30
+ }
31
+
32
+ const normalizedDomain = normalizeDomain(auth0Domain);
33
+
34
+ return jwksClient({
35
+ jwksUri: `https://${normalizedDomain}/.well-known/jwks.json`,
36
+ cache: true,
37
+ cacheMaxAge: 86400000, // 24 hours
38
+ });
39
+ };
40
+
41
+ /**
42
+ * Get signing key for JWT verification
43
+ */
44
+ const getKey = (header, callback) => {
45
+ const client = getJwksClient();
46
+
47
+ client.getSigningKey(header.kid, (err, key) => {
48
+ if (err) {
49
+ callback(err);
50
+ return;
51
+ }
52
+ const signingKey = key?.getPublicKey();
53
+ callback(null, signingKey);
54
+ });
55
+ };
56
+
57
+ /**
58
+ * Verify Auth0 JWT token
59
+ */
60
+ export const verifyAuth0Token = (token) => {
61
+ return new Promise((resolve, reject) => {
62
+ const auth0Config = Meteor.settings?.auth0;
63
+
64
+ if (!auth0Config?.domain || !auth0Config?.clientId) {
65
+ reject(new Meteor.Error('auth0-config-missing', 'Auth0 configuration missing'));
66
+ return;
67
+ }
68
+
69
+ const normalizedDomain = normalizeDomain(auth0Config.domain);
70
+
71
+ jwt.verify(
72
+ token,
73
+ getKey,
74
+ {
75
+ audience: auth0Config.clientId,
76
+ issuer: `https://${normalizedDomain}/`,
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);
85
+ }
86
+ );
87
+ });
88
+ };
89
+
90
+ /**
91
+ * Find or create a Meteor user from Auth0 data
92
+ */
93
+ export const findOrCreateUserFromAuth0 = async (tokenPayload) => {
94
+ const auth0Id = tokenPayload.sub;
95
+ const email = tokenPayload.email;
96
+ const now = new Date();
97
+
98
+ // Try to find existing user by Auth0 ID
99
+ let user = await Meteor.users.findOneAsync({
100
+ 'services.auth0.id': auth0Id,
101
+ });
102
+
103
+ if (user) {
104
+ // Update user profile and token data
105
+ await Meteor.users.updateAsync(user._id, {
106
+ $set: {
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),
119
+ },
120
+ });
121
+ return user._id;
122
+ }
123
+
124
+ // Try to find by email if email exists
125
+ if (email) {
126
+ user = await Meteor.users.findOneAsync({
127
+ 'emails.address': email,
128
+ });
129
+
130
+ if (user) {
131
+ // Link Auth0 account to existing user
132
+ await Meteor.users.updateAsync(user._id, {
133
+ $set: {
134
+ // Profile data (client-accessible)
135
+ 'profile.auth0': {
136
+ name: tokenPayload.name,
137
+ picture: tokenPayload.picture,
138
+ email: email,
139
+ email_verified: tokenPayload.email_verified,
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
+ },
148
+ },
149
+ });
150
+ return user._id;
151
+ }
152
+ }
153
+
154
+ // Create new user
155
+ const userId = Accounts.insertUserDoc({}, {
156
+ emails: email ? [{ address: email, verified: tokenPayload.email_verified || false }] : [],
157
+ profile: {
158
+ name: tokenPayload.name || tokenPayload.nickname || 'Auth0 User',
159
+ // Auth0 profile data (client-accessible)
160
+ auth0: {
161
+ name: tokenPayload.name,
162
+ picture: tokenPayload.picture,
163
+ email: email,
164
+ email_verified: tokenPayload.email_verified,
165
+ },
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
+ },
176
+ });
177
+
178
+ return userId;
179
+ };
@@ -0,0 +1,194 @@
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
+ * 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
+
35
+ /**
36
+ * Create JWKS client for verifying Auth0 tokens
37
+ */
38
+ const getJwksClient = () => {
39
+ const auth0Domain = Meteor.settings?.auth0?.domain;
40
+
41
+ if (!auth0Domain) {
42
+ throw new Meteor.Error('auth0-config-missing', 'Auth0 domain not configured');
43
+ }
44
+
45
+ const normalizedDomain = normalizeDomain(auth0Domain);
46
+
47
+ return jwksClient({
48
+ jwksUri: `https://${normalizedDomain}/.well-known/jwks.json`,
49
+ cache: true,
50
+ cacheMaxAge: 86400000, // 24 hours
51
+ });
52
+ };
53
+
54
+ /**
55
+ * Get signing key for JWT verification
56
+ */
57
+ const getKey = (header: any, callback: any) => {
58
+ const client = getJwksClient();
59
+
60
+ client.getSigningKey(header.kid, (err, key) => {
61
+ if (err) {
62
+ callback(err);
63
+ return;
64
+ }
65
+ const signingKey = key?.getPublicKey();
66
+ callback(null, signingKey);
67
+ });
68
+ };
69
+
70
+ /**
71
+ * Verify Auth0 JWT token
72
+ */
73
+ export const verifyAuth0Token = (token: string): Promise<Auth0TokenPayload> => {
74
+ return new Promise((resolve, reject) => {
75
+ const auth0Config = Meteor.settings?.auth0;
76
+
77
+ if (!auth0Config?.domain || !auth0Config?.clientId) {
78
+ reject(new Meteor.Error('auth0-config-missing', 'Auth0 configuration missing'));
79
+ return;
80
+ }
81
+
82
+ const normalizedDomain = normalizeDomain(auth0Config.domain);
83
+
84
+ jwt.verify(
85
+ token,
86
+ getKey,
87
+ {
88
+ audience: auth0Config.clientId,
89
+ issuer: `https://${normalizedDomain}/`,
90
+ algorithms: ['RS256'],
91
+ },
92
+ (err, decoded) => {
93
+ if (err) {
94
+ reject(new Meteor.Error('auth0-invalid-token', 'Invalid Auth0 token', err));
95
+ return;
96
+ }
97
+ resolve(decoded as Auth0TokenPayload);
98
+ }
99
+ );
100
+ });
101
+ };
102
+
103
+ /**
104
+ * Find or create a Meteor user from Auth0 data
105
+ */
106
+ export const findOrCreateUserFromAuth0 = async (
107
+ tokenPayload: Auth0TokenPayload
108
+ ): Promise<string> => {
109
+ const auth0Id = tokenPayload.sub;
110
+ const email = tokenPayload.email;
111
+ const now = new Date();
112
+
113
+ // Try to find existing user by Auth0 ID
114
+ let user = await Meteor.users.findOneAsync({
115
+ 'services.auth0.id': auth0Id,
116
+ });
117
+
118
+ if (user) {
119
+ // Update user profile and token data
120
+ await Meteor.users.updateAsync(user._id, {
121
+ $set: {
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),
134
+ },
135
+ });
136
+ return user._id;
137
+ }
138
+
139
+ // Try to find by email if email exists
140
+ if (email) {
141
+ user = await Meteor.users.findOneAsync({
142
+ 'emails.address': email,
143
+ });
144
+
145
+ if (user) {
146
+ // Link Auth0 account to existing user
147
+ await Meteor.users.updateAsync(user._id, {
148
+ $set: {
149
+ // Profile data (client-accessible)
150
+ 'profile.auth0': {
151
+ name: tokenPayload.name,
152
+ picture: tokenPayload.picture,
153
+ email: email,
154
+ email_verified: tokenPayload.email_verified,
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
+ },
163
+ },
164
+ });
165
+ return user._id;
166
+ }
167
+ }
168
+
169
+ // Create new user
170
+ const userId = Accounts.insertUserDoc({}, {
171
+ emails: email ? [{ address: email, verified: tokenPayload.email_verified || false }] : [],
172
+ profile: {
173
+ name: tokenPayload.name || tokenPayload.nickname || 'Auth0 User',
174
+ // Auth0 profile data (client-accessible)
175
+ auth0: {
176
+ name: tokenPayload.name,
177
+ picture: tokenPayload.picture,
178
+ email: email,
179
+ email_verified: tokenPayload.email_verified,
180
+ },
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
+ },
191
+ });
192
+
193
+ return userId;
194
+ };
@@ -0,0 +1,71 @@
1
+ import React, { useEffect, useState } from 'react';
2
+ import { useNavigate } from 'react-router-dom';
3
+ import { Box, CircularProgress, Typography, Alert } from '@mui/material';
4
+ import AuthService from '<%= authServicePath %>';
5
+
6
+ /**
7
+ * Auth0 Callback Component
8
+ *
9
+ * This component handles the OAuth callback from Auth0.
10
+ * It exchanges the authorization code for tokens and logs the user into Meteor.
11
+ */
12
+ export const AuthCallback = () => {
13
+ const navigate = useNavigate();
14
+ const [error, setError] = useState(null);
15
+
16
+ useEffect(() => {
17
+ const handleCallback = async () => {
18
+ try {
19
+ // Handle the Auth0 callback and login to Meteor
20
+ await AuthService.handleAuth0Callback();
21
+
22
+ // Redirect to home page after successful login
23
+ navigate('/', { replace: true });
24
+ } catch (err) {
25
+ console.error('Auth0 callback error:', err);
26
+ setError(err instanceof Error ? err.message : 'Authentication failed');
27
+
28
+ // Redirect to login page after a delay
29
+ setTimeout(() => {
30
+ navigate('/login', { replace: true });
31
+ }, 3000);
32
+ }
33
+ };
34
+
35
+ handleCallback();
36
+ }, [navigate]);
37
+
38
+ return (
39
+ <Box
40
+ sx={{
41
+ display: 'flex',
42
+ flexDirection: 'column',
43
+ alignItems: 'center',
44
+ justifyContent: 'center',
45
+ minHeight: '100vh',
46
+ gap: 2,
47
+ }}
48
+ >
49
+ {error ? (
50
+ <>
51
+ <Alert severity="error" sx={{ maxWidth: 600 }}>
52
+ {error}
53
+ </Alert>
54
+ <Typography variant="body2" color="text.secondary">
55
+ Redirecting to login page...
56
+ </Typography>
57
+ </>
58
+ ) : (
59
+ <>
60
+ <CircularProgress size={60} />
61
+ <Typography variant="h6" sx={{ mt: 2 }}>
62
+ Completing authentication...
63
+ </Typography>
64
+ <Typography variant="body2" color="text.secondary">
65
+ Please wait while we log you in.
66
+ </Typography>
67
+ </>
68
+ )}
69
+ </Box>
70
+ );
71
+ };
@@ -0,0 +1,71 @@
1
+ import React, { useEffect, useState } from 'react';
2
+ import { useNavigate } from 'react-router-dom';
3
+ import { Box, CircularProgress, Typography, Alert } from '@mui/material';
4
+ import AuthService from '<%= authServicePath %>';
5
+
6
+ /**
7
+ * Auth0 Callback Component
8
+ *
9
+ * This component handles the OAuth callback from Auth0.
10
+ * It exchanges the authorization code for tokens and logs the user into Meteor.
11
+ */
12
+ export const AuthCallback = () => {
13
+ const navigate = useNavigate();
14
+ const [error, setError] = useState<string | null>(null);
15
+
16
+ useEffect(() => {
17
+ const handleCallback = async () => {
18
+ try {
19
+ // Handle the Auth0 callback and login to Meteor
20
+ await AuthService.handleAuth0Callback();
21
+
22
+ // Redirect to home page after successful login
23
+ navigate('/', { replace: true });
24
+ } catch (err) {
25
+ console.error('Auth0 callback error:', err);
26
+ setError(err instanceof Error ? err.message : 'Authentication failed');
27
+
28
+ // Redirect to login page after a delay
29
+ setTimeout(() => {
30
+ navigate('/login', { replace: true });
31
+ }, 3000);
32
+ }
33
+ };
34
+
35
+ handleCallback();
36
+ }, [navigate]);
37
+
38
+ return (
39
+ <Box
40
+ sx={{
41
+ display: 'flex',
42
+ flexDirection: 'column',
43
+ alignItems: 'center',
44
+ justifyContent: 'center',
45
+ minHeight: '100vh',
46
+ gap: 2,
47
+ }}
48
+ >
49
+ {error ? (
50
+ <>
51
+ <Alert severity="error" sx={{ maxWidth: 600 }}>
52
+ {error}
53
+ </Alert>
54
+ <Typography variant="body2" color="text.secondary">
55
+ Redirecting to login page...
56
+ </Typography>
57
+ </>
58
+ ) : (
59
+ <>
60
+ <CircularProgress size={60} />
61
+ <Typography variant="h6" sx={{ mt: 2 }}>
62
+ Completing authentication...
63
+ </Typography>
64
+ <Typography variant="body2" color="text.secondary">
65
+ Please wait while we log you in.
66
+ </Typography>
67
+ </>
68
+ )}
69
+ </Box>
70
+ );
71
+ };
@@ -0,0 +1,123 @@
1
+ import React, { useContext, useState } from 'react';
2
+ import {
3
+ Box,
4
+ Card,
5
+ CardContent,
6
+ Typography,
7
+ Button,
8
+ Alert,
9
+ CircularProgress,
10
+ } from '@mui/material';
11
+ import { Meteor } from 'meteor/meteor';
12
+ import { AuthContext } from '<%= authContextPath %>';
13
+
14
+ /**
15
+ * Auth0 Account Settings Component
16
+ *
17
+ * Displays Auth0 account status and allows linking/unlinking
18
+ * Auth0 account to/from the current Meteor user.
19
+ */
20
+ export const Auth0AccountSettings = () => {
21
+ const auth = useContext(AuthContext);
22
+ const [loading, setLoading] = useState(false);
23
+ const [message, setMessage] = useState(null);
24
+
25
+ if (!auth?.currentUser) {
26
+ return null;
27
+ }
28
+
29
+ const user = Meteor.user();
30
+ const hasAuth0Account = Boolean(user?.services?.auth0);
31
+
32
+ const handleLinkAccount = async () => {
33
+ setLoading(true);
34
+ setMessage(null);
35
+
36
+ try {
37
+ const success = await auth.linkAuth0Account?.();
38
+ if (success) {
39
+ setMessage({ type: 'success', text: 'Auth0 account linked successfully!' });
40
+ }
41
+ } catch (error) {
42
+ setMessage({
43
+ type: 'error',
44
+ text: error instanceof Error ? error.message : 'Failed to link Auth0 account',
45
+ });
46
+ } finally {
47
+ setLoading(false);
48
+ }
49
+ };
50
+
51
+ const handleUnlinkAccount = async () => {
52
+ setLoading(true);
53
+ setMessage(null);
54
+
55
+ try {
56
+ const success = await auth.unlinkAuth0Account?.();
57
+ if (success) {
58
+ setMessage({ type: 'success', text: 'Auth0 account unlinked successfully!' });
59
+ }
60
+ } catch (error) {
61
+ setMessage({
62
+ type: 'error',
63
+ text: error instanceof Error ? error.message : 'Failed to unlink Auth0 account',
64
+ });
65
+ } finally {
66
+ setLoading(false);
67
+ }
68
+ };
69
+
70
+ return (
71
+ <Card>
72
+ <CardContent>
73
+ <Typography variant="h6" gutterBottom>
74
+ Auth0 Integration
75
+ </Typography>
76
+
77
+ {message && (
78
+ <Alert severity={message.type} sx={{ mb: 2 }}>
79
+ {message.text}
80
+ </Alert>
81
+ )}
82
+
83
+ {hasAuth0Account ? (
84
+ <Box>
85
+ <Alert severity="success" sx={{ mb: 2 }}>
86
+ Your account is linked with Auth0
87
+ </Alert>
88
+ <Typography variant="body2" color="text.secondary" gutterBottom>
89
+ Email: {user?.services?.auth0?.email || 'N/A'}
90
+ </Typography>
91
+ <Typography variant="body2" color="text.secondary" gutterBottom>
92
+ Name: {user?.services?.auth0?.name || 'N/A'}
93
+ </Typography>
94
+ <Button
95
+ variant="outlined"
96
+ color="error"
97
+ onClick={handleUnlinkAccount}
98
+ disabled={loading}
99
+ sx={{ mt: 2 }}
100
+ startIcon={loading ? <CircularProgress size={20} /> : undefined}
101
+ >
102
+ {loading ? 'Unlinking...' : 'Unlink Auth0 Account'}
103
+ </Button>
104
+ </Box>
105
+ ) : (
106
+ <Box>
107
+ <Alert severity="info" sx={{ mb: 2 }}>
108
+ Link your account with Auth0 for easier login
109
+ </Alert>
110
+ <Button
111
+ variant="contained"
112
+ onClick={handleLinkAccount}
113
+ disabled={loading}
114
+ startIcon={loading ? <CircularProgress size={20} /> : undefined}
115
+ >
116
+ {loading ? 'Linking...' : 'Link Auth0 Account'}
117
+ </Button>
118
+ </Box>
119
+ )}
120
+ </CardContent>
121
+ </Card>
122
+ );
123
+ };