@startanaicompany/cli 1.0.0 → 1.1.0
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.md +253 -0
- package/README.md +36 -6
- package/auth_session_update.md +785 -0
- package/bin/saac.js +14 -1
- package/package.json +1 -1
- package/src/commands/login.js +38 -44
- package/src/commands/logout.js +41 -3
- package/src/commands/logoutAll.js +74 -0
- package/src/commands/register.js +46 -34
- package/src/commands/sessions.js +75 -0
- package/src/commands/verify.js +32 -4
- package/src/lib/api.js +37 -1
- package/src/lib/config.js +52 -4
- package/test-session-token.js +117 -0
package/bin/saac.js
CHANGED
|
@@ -14,6 +14,8 @@ const register = require('../src/commands/register');
|
|
|
14
14
|
const login = require('../src/commands/login');
|
|
15
15
|
const verify = require('../src/commands/verify');
|
|
16
16
|
const logout = require('../src/commands/logout');
|
|
17
|
+
const logoutAll = require('../src/commands/logoutAll');
|
|
18
|
+
const sessions = require('../src/commands/sessions');
|
|
17
19
|
const init = require('../src/commands/init');
|
|
18
20
|
const create = require('../src/commands/create');
|
|
19
21
|
const deploy = require('../src/commands/deploy');
|
|
@@ -54,9 +56,20 @@ program
|
|
|
54
56
|
|
|
55
57
|
program
|
|
56
58
|
.command('logout')
|
|
57
|
-
.description('
|
|
59
|
+
.description('Logout from current device')
|
|
58
60
|
.action(logout);
|
|
59
61
|
|
|
62
|
+
program
|
|
63
|
+
.command('logout-all')
|
|
64
|
+
.description('Logout from all devices (revoke all sessions)')
|
|
65
|
+
.option('-y, --yes', 'Skip confirmation')
|
|
66
|
+
.action(logoutAll);
|
|
67
|
+
|
|
68
|
+
program
|
|
69
|
+
.command('sessions')
|
|
70
|
+
.description('List all active sessions')
|
|
71
|
+
.action(sessions);
|
|
72
|
+
|
|
60
73
|
// Application management
|
|
61
74
|
program
|
|
62
75
|
.command('init')
|
package/package.json
CHANGED
package/src/commands/login.js
CHANGED
|
@@ -10,65 +10,59 @@ const logger = require('../lib/logger');
|
|
|
10
10
|
|
|
11
11
|
async function login(options) {
|
|
12
12
|
try {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
if (!validator.isEmail(input)) {
|
|
25
|
-
return 'Please enter a valid email address';
|
|
26
|
-
}
|
|
27
|
-
return true;
|
|
28
|
-
},
|
|
29
|
-
},
|
|
30
|
-
]);
|
|
31
|
-
email = answers.email;
|
|
13
|
+
// Require both email and API key flags (no interactive prompts)
|
|
14
|
+
if (!options.email || !options.apiKey) {
|
|
15
|
+
logger.error('Email and API key are required');
|
|
16
|
+
logger.newline();
|
|
17
|
+
logger.info('Usage:');
|
|
18
|
+
logger.log(' saac login -e <email> -k <api-key>');
|
|
19
|
+
logger.log(' saac login --email <email> --api-key <api-key>');
|
|
20
|
+
logger.newline();
|
|
21
|
+
logger.info('Example:');
|
|
22
|
+
logger.log(' saac login -e user@example.com -k cw_your_api_key');
|
|
23
|
+
process.exit(1);
|
|
32
24
|
}
|
|
33
25
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
message: 'API Key:',
|
|
42
|
-
mask: '*',
|
|
43
|
-
},
|
|
44
|
-
]);
|
|
45
|
-
apiKey = answers.apiKey;
|
|
26
|
+
const email = options.email;
|
|
27
|
+
const apiKey = options.apiKey;
|
|
28
|
+
|
|
29
|
+
// Validate email format
|
|
30
|
+
if (!validator.isEmail(email)) {
|
|
31
|
+
logger.error('Invalid email address');
|
|
32
|
+
process.exit(1);
|
|
46
33
|
}
|
|
47
34
|
|
|
48
|
-
|
|
49
|
-
const spin = logger.spinner('Verifying credentials...').start();
|
|
35
|
+
logger.section('Login to StartAnAiCompany');
|
|
50
36
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
saveUser({ email, apiKey });
|
|
37
|
+
// Login and get session token
|
|
38
|
+
const spin = logger.spinner('Logging in...').start();
|
|
54
39
|
|
|
55
|
-
|
|
40
|
+
try {
|
|
41
|
+
// Call /auth/login endpoint to get session token
|
|
42
|
+
const result = await api.login(email, apiKey);
|
|
56
43
|
|
|
57
44
|
spin.succeed('Login successful!');
|
|
58
45
|
|
|
59
|
-
//
|
|
46
|
+
// Save session token and expiration
|
|
60
47
|
saveUser({
|
|
61
|
-
email:
|
|
62
|
-
userId:
|
|
63
|
-
|
|
64
|
-
|
|
48
|
+
email: result.user.email || email,
|
|
49
|
+
userId: result.user.id,
|
|
50
|
+
sessionToken: result.session_token,
|
|
51
|
+
expiresAt: result.expires_at,
|
|
52
|
+
verified: result.user.verified,
|
|
65
53
|
});
|
|
66
54
|
|
|
67
55
|
logger.newline();
|
|
68
56
|
logger.success('You are now logged in!');
|
|
69
57
|
logger.newline();
|
|
70
|
-
logger.field('Email',
|
|
71
|
-
logger.field('Verified',
|
|
58
|
+
logger.field('Email', result.user.email || email);
|
|
59
|
+
logger.field('Verified', result.user.verified ? 'Yes' : 'No');
|
|
60
|
+
|
|
61
|
+
// Show expiration date
|
|
62
|
+
if (result.expires_at) {
|
|
63
|
+
const expirationDate = new Date(result.expires_at);
|
|
64
|
+
logger.field('Session expires', expirationDate.toLocaleDateString());
|
|
65
|
+
}
|
|
72
66
|
|
|
73
67
|
} catch (error) {
|
|
74
68
|
spin.fail('Login failed');
|
package/src/commands/logout.js
CHANGED
|
@@ -1,9 +1,47 @@
|
|
|
1
|
-
const
|
|
1
|
+
const api = require('../lib/api');
|
|
2
|
+
const { clearUser, getUser } = require('../lib/config');
|
|
2
3
|
const logger = require('../lib/logger');
|
|
3
4
|
|
|
4
5
|
async function logout() {
|
|
5
|
-
|
|
6
|
-
|
|
6
|
+
try {
|
|
7
|
+
const user = getUser();
|
|
8
|
+
|
|
9
|
+
if (!user || (!user.sessionToken && !user.apiKey)) {
|
|
10
|
+
logger.warn('You are not logged in');
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
logger.section('Logout from StartAnAiCompany');
|
|
15
|
+
|
|
16
|
+
const spin = logger.spinner('Logging out...').start();
|
|
17
|
+
|
|
18
|
+
try {
|
|
19
|
+
// Try to revoke session on server
|
|
20
|
+
const client = api.createClient();
|
|
21
|
+
await client.post('/auth/logout');
|
|
22
|
+
|
|
23
|
+
// Clear local config
|
|
24
|
+
clearUser();
|
|
25
|
+
|
|
26
|
+
spin.succeed('Logout successful!');
|
|
27
|
+
logger.success('You have been logged out from this device');
|
|
28
|
+
|
|
29
|
+
} catch (error) {
|
|
30
|
+
// Even if server call fails, clear local config
|
|
31
|
+
clearUser();
|
|
32
|
+
|
|
33
|
+
if (error.response?.status === 401) {
|
|
34
|
+
// Session already invalid
|
|
35
|
+
spin.succeed('Logged out locally (session was expired)');
|
|
36
|
+
} else {
|
|
37
|
+
spin.warn('Logged out locally (server error)');
|
|
38
|
+
logger.warn('Session may still be active on server');
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
} catch (error) {
|
|
42
|
+
logger.error(error.message);
|
|
43
|
+
process.exit(1);
|
|
44
|
+
}
|
|
7
45
|
}
|
|
8
46
|
|
|
9
47
|
module.exports = logout;
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Logout All command - Revoke all sessions
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const inquirer = require('inquirer');
|
|
6
|
+
const api = require('../lib/api');
|
|
7
|
+
const { clearUser, isAuthenticated } = require('../lib/config');
|
|
8
|
+
const logger = require('../lib/logger');
|
|
9
|
+
|
|
10
|
+
async function logoutAll(options) {
|
|
11
|
+
try {
|
|
12
|
+
// Check authentication
|
|
13
|
+
if (!isAuthenticated()) {
|
|
14
|
+
logger.error('Not logged in');
|
|
15
|
+
process.exit(1);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
logger.section('Logout from All Devices');
|
|
19
|
+
|
|
20
|
+
// Confirm unless --yes flag is provided
|
|
21
|
+
if (!options.yes) {
|
|
22
|
+
logger.warn('This will logout from ALL devices where you are logged in');
|
|
23
|
+
logger.newline();
|
|
24
|
+
|
|
25
|
+
const answers = await inquirer.prompt([
|
|
26
|
+
{
|
|
27
|
+
type: 'confirm',
|
|
28
|
+
name: 'confirm',
|
|
29
|
+
message: 'Are you sure you want to continue?',
|
|
30
|
+
default: false,
|
|
31
|
+
},
|
|
32
|
+
]);
|
|
33
|
+
|
|
34
|
+
if (!answers.confirm) {
|
|
35
|
+
logger.info('Cancelled');
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const spin = logger.spinner('Revoking all sessions...').start();
|
|
41
|
+
|
|
42
|
+
try {
|
|
43
|
+
const client = api.createClient();
|
|
44
|
+
const response = await client.post('/auth/logout-all');
|
|
45
|
+
|
|
46
|
+
// Clear local config
|
|
47
|
+
clearUser();
|
|
48
|
+
|
|
49
|
+
const count = response.data.sessions_revoked || 0;
|
|
50
|
+
spin.succeed(`Logged out from ${count} device(s)!`);
|
|
51
|
+
|
|
52
|
+
logger.newline();
|
|
53
|
+
logger.success('All sessions have been revoked');
|
|
54
|
+
logger.info('You will need to login again on all devices');
|
|
55
|
+
|
|
56
|
+
} catch (error) {
|
|
57
|
+
// Even if server call fails, clear local config
|
|
58
|
+
clearUser();
|
|
59
|
+
|
|
60
|
+
if (error.response?.status === 401) {
|
|
61
|
+
spin.succeed('Logged out locally (session was expired)');
|
|
62
|
+
} else {
|
|
63
|
+
spin.fail('Failed to revoke sessions on server');
|
|
64
|
+
logger.warn('Logged out locally, but server sessions may still be active');
|
|
65
|
+
throw error;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
} catch (error) {
|
|
69
|
+
logger.error(error.response?.data?.message || error.message);
|
|
70
|
+
process.exit(1);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
module.exports = logoutAll;
|
package/src/commands/register.js
CHANGED
|
@@ -10,40 +10,32 @@ const logger = require('../lib/logger');
|
|
|
10
10
|
|
|
11
11
|
async function register(options) {
|
|
12
12
|
try {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
return 'Please enter a valid email address';
|
|
26
|
-
}
|
|
27
|
-
return true;
|
|
28
|
-
},
|
|
29
|
-
},
|
|
30
|
-
]);
|
|
31
|
-
email = answers.email;
|
|
13
|
+
// Require email flag (no interactive prompt)
|
|
14
|
+
if (!options.email) {
|
|
15
|
+
logger.error('Email is required');
|
|
16
|
+
logger.newline();
|
|
17
|
+
logger.info('Usage:');
|
|
18
|
+
logger.log(' saac register -e <email>');
|
|
19
|
+
logger.log(' saac register --email <email> [--gitea-username <username>]');
|
|
20
|
+
logger.newline();
|
|
21
|
+
logger.info('Example:');
|
|
22
|
+
logger.log(' saac register -e user@example.com');
|
|
23
|
+
logger.log(' saac register -e user@example.com --gitea-username myuser');
|
|
24
|
+
process.exit(1);
|
|
32
25
|
}
|
|
33
26
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
name: 'giteaUsername',
|
|
41
|
-
message: 'Gitea username (optional, press Enter to skip):',
|
|
42
|
-
},
|
|
43
|
-
]);
|
|
44
|
-
giteaUsername = answers.giteaUsername || undefined;
|
|
27
|
+
const email = options.email;
|
|
28
|
+
|
|
29
|
+
// Validate email format
|
|
30
|
+
if (!validator.isEmail(email)) {
|
|
31
|
+
logger.error('Invalid email address');
|
|
32
|
+
process.exit(1);
|
|
45
33
|
}
|
|
46
34
|
|
|
35
|
+
const giteaUsername = options.giteaUsername || undefined;
|
|
36
|
+
|
|
37
|
+
logger.section('Register for StartAnAiCompany');
|
|
38
|
+
|
|
47
39
|
// Register via API
|
|
48
40
|
const spin = logger.spinner('Creating account...').start();
|
|
49
41
|
|
|
@@ -53,12 +45,22 @@ async function register(options) {
|
|
|
53
45
|
spin.succeed('Account created successfully!');
|
|
54
46
|
|
|
55
47
|
// Save user info
|
|
56
|
-
|
|
48
|
+
// If backend returns session_token, use it; otherwise fall back to api_key
|
|
49
|
+
const userData = {
|
|
57
50
|
email: result.email || email,
|
|
58
51
|
userId: result.user_id,
|
|
59
|
-
apiKey: result.api_key,
|
|
60
52
|
verified: result.verified || false,
|
|
61
|
-
}
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
if (result.session_token) {
|
|
56
|
+
userData.sessionToken = result.session_token;
|
|
57
|
+
userData.expiresAt = result.expires_at;
|
|
58
|
+
} else {
|
|
59
|
+
// Backward compatibility: use API key if session token not provided
|
|
60
|
+
userData.apiKey = result.api_key;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
saveUser(userData);
|
|
62
64
|
|
|
63
65
|
logger.newline();
|
|
64
66
|
logger.success('Registration complete!');
|
|
@@ -81,7 +83,17 @@ async function register(options) {
|
|
|
81
83
|
|
|
82
84
|
logger.newline();
|
|
83
85
|
logger.field('Email', email);
|
|
84
|
-
|
|
86
|
+
|
|
87
|
+
// Show session token or API key info
|
|
88
|
+
if (result.session_token) {
|
|
89
|
+
logger.field('Session Token', result.session_token.substring(0, 20) + '...');
|
|
90
|
+
if (result.expires_at) {
|
|
91
|
+
const expirationDate = new Date(result.expires_at);
|
|
92
|
+
logger.field('Expires', expirationDate.toLocaleDateString());
|
|
93
|
+
}
|
|
94
|
+
} else if (result.api_key) {
|
|
95
|
+
logger.field('API Key', result.api_key.substring(0, 20) + '...');
|
|
96
|
+
}
|
|
85
97
|
|
|
86
98
|
} catch (error) {
|
|
87
99
|
spin.fail('Registration failed');
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sessions command - List active sessions
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const api = require('../lib/api');
|
|
6
|
+
const { isAuthenticated } = require('../lib/config');
|
|
7
|
+
const logger = require('../lib/logger');
|
|
8
|
+
const { table } = require('table');
|
|
9
|
+
|
|
10
|
+
async function sessions() {
|
|
11
|
+
try {
|
|
12
|
+
// Check authentication
|
|
13
|
+
if (!isAuthenticated()) {
|
|
14
|
+
logger.error('Not logged in');
|
|
15
|
+
logger.newline();
|
|
16
|
+
logger.info('Run:');
|
|
17
|
+
logger.log(' saac login -e <email> -k <api-key>');
|
|
18
|
+
process.exit(1);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
logger.section('Active Sessions');
|
|
22
|
+
|
|
23
|
+
const spin = logger.spinner('Fetching sessions...').start();
|
|
24
|
+
|
|
25
|
+
try {
|
|
26
|
+
const client = api.createClient();
|
|
27
|
+
const response = await client.get('/auth/sessions');
|
|
28
|
+
const { sessions: sessionList, total } = response.data;
|
|
29
|
+
|
|
30
|
+
spin.succeed(`Found ${total} active session(s)`);
|
|
31
|
+
|
|
32
|
+
if (total === 0) {
|
|
33
|
+
logger.newline();
|
|
34
|
+
logger.info('No active sessions');
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
logger.newline();
|
|
39
|
+
|
|
40
|
+
// Format sessions as table
|
|
41
|
+
const data = [
|
|
42
|
+
['Created', 'Last Used', 'IP Address', 'Expires'],
|
|
43
|
+
];
|
|
44
|
+
|
|
45
|
+
sessionList.forEach((session) => {
|
|
46
|
+
const created = new Date(session.created_at).toLocaleDateString();
|
|
47
|
+
const lastUsed = new Date(session.last_used_at).toLocaleString();
|
|
48
|
+
const expires = new Date(session.expires_at).toLocaleDateString();
|
|
49
|
+
const ip = session.created_ip || 'Unknown';
|
|
50
|
+
|
|
51
|
+
data.push([created, lastUsed, ip, expires]);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
console.log(table(data, {
|
|
55
|
+
header: {
|
|
56
|
+
alignment: 'center',
|
|
57
|
+
content: `${total} Active Session(s)`,
|
|
58
|
+
},
|
|
59
|
+
}));
|
|
60
|
+
|
|
61
|
+
logger.newline();
|
|
62
|
+
logger.info('Tip: Use `saac logout` to logout from current device');
|
|
63
|
+
logger.info(' Use `saac logout-all` to logout from all devices');
|
|
64
|
+
|
|
65
|
+
} catch (error) {
|
|
66
|
+
spin.fail('Failed to fetch sessions');
|
|
67
|
+
throw error;
|
|
68
|
+
}
|
|
69
|
+
} catch (error) {
|
|
70
|
+
logger.error(error.response?.data?.message || error.message);
|
|
71
|
+
process.exit(1);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
module.exports = sessions;
|
package/src/commands/verify.js
CHANGED
|
@@ -8,10 +8,24 @@ const logger = require('../lib/logger');
|
|
|
8
8
|
|
|
9
9
|
async function verify(code) {
|
|
10
10
|
try {
|
|
11
|
+
if (!code) {
|
|
12
|
+
logger.error('Verification code is required');
|
|
13
|
+
logger.newline();
|
|
14
|
+
logger.info('Usage:');
|
|
15
|
+
logger.log(' saac verify <code>');
|
|
16
|
+
logger.newline();
|
|
17
|
+
logger.info('Example:');
|
|
18
|
+
logger.log(' saac verify 123456');
|
|
19
|
+
process.exit(1);
|
|
20
|
+
}
|
|
21
|
+
|
|
11
22
|
const user = getUser();
|
|
12
23
|
|
|
13
24
|
if (!user || !user.email) {
|
|
14
|
-
logger.error('No user found. Please register first
|
|
25
|
+
logger.error('No user found. Please register first');
|
|
26
|
+
logger.newline();
|
|
27
|
+
logger.info('Run:');
|
|
28
|
+
logger.log(' saac register -e <email>');
|
|
15
29
|
process.exit(1);
|
|
16
30
|
}
|
|
17
31
|
|
|
@@ -29,16 +43,30 @@ async function verify(code) {
|
|
|
29
43
|
|
|
30
44
|
spin.succeed('Email verified successfully!');
|
|
31
45
|
|
|
32
|
-
// Update user verification status
|
|
33
|
-
|
|
46
|
+
// Update user verification status and session token if provided
|
|
47
|
+
const updatedUser = {
|
|
34
48
|
...user,
|
|
35
49
|
verified: true,
|
|
36
|
-
}
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
// If backend returns new session token after verification, update it
|
|
53
|
+
if (result.session_token) {
|
|
54
|
+
updatedUser.sessionToken = result.session_token;
|
|
55
|
+
updatedUser.expiresAt = result.expires_at;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
saveUser(updatedUser);
|
|
37
59
|
|
|
38
60
|
logger.newline();
|
|
39
61
|
logger.success('Your account is now verified!');
|
|
40
62
|
logger.info('You can now create and deploy applications.');
|
|
41
63
|
|
|
64
|
+
// Show expiration if session token was updated
|
|
65
|
+
if (result.expires_at) {
|
|
66
|
+
const expirationDate = new Date(result.expires_at);
|
|
67
|
+
logger.field('Session expires', expirationDate.toLocaleDateString());
|
|
68
|
+
}
|
|
69
|
+
|
|
42
70
|
} catch (error) {
|
|
43
71
|
spin.fail('Verification failed');
|
|
44
72
|
throw error;
|
package/src/lib/api.js
CHANGED
|
@@ -3,22 +3,56 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
const axios = require('axios');
|
|
6
|
+
const os = require('os');
|
|
6
7
|
const { getApiUrl, getUser } = require('./config');
|
|
8
|
+
const pkg = require('../../package.json');
|
|
7
9
|
|
|
8
10
|
/**
|
|
9
11
|
* Create axios instance with base configuration
|
|
10
12
|
*/
|
|
11
13
|
function createClient() {
|
|
12
14
|
const user = getUser();
|
|
15
|
+
const envApiKey = process.env.SAAC_API_KEY; // For CI/CD
|
|
16
|
+
|
|
17
|
+
const headers = {
|
|
18
|
+
'Content-Type': 'application/json',
|
|
19
|
+
'User-Agent': `saac-cli/${pkg.version} (${os.platform()}; ${os.arch()})`,
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
// Priority order:
|
|
23
|
+
// 1. Environment variable (for CI/CD, scripts)
|
|
24
|
+
// 2. Session token (for CLI users)
|
|
25
|
+
// 3. API key (backward compatibility)
|
|
26
|
+
if (envApiKey) {
|
|
27
|
+
headers['X-API-Key'] = envApiKey;
|
|
28
|
+
} else if (user?.sessionToken) {
|
|
29
|
+
headers['X-Session-Token'] = user.sessionToken;
|
|
30
|
+
} else if (user?.apiKey) {
|
|
31
|
+
headers['X-API-Key'] = user.apiKey;
|
|
32
|
+
}
|
|
13
33
|
|
|
14
34
|
return axios.create({
|
|
35
|
+
baseURL: getApiUrl(),
|
|
36
|
+
timeout: 30000,
|
|
37
|
+
headers,
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Login and get session token
|
|
43
|
+
*/
|
|
44
|
+
async function login(email, apiKey) {
|
|
45
|
+
const client = axios.create({
|
|
15
46
|
baseURL: getApiUrl(),
|
|
16
47
|
timeout: 30000,
|
|
17
48
|
headers: {
|
|
18
49
|
'Content-Type': 'application/json',
|
|
19
|
-
|
|
50
|
+
'X-API-Key': apiKey, // Use API key for login
|
|
20
51
|
},
|
|
21
52
|
});
|
|
53
|
+
|
|
54
|
+
const response = await client.post('/auth/login', { email });
|
|
55
|
+
return response.data;
|
|
22
56
|
}
|
|
23
57
|
|
|
24
58
|
/**
|
|
@@ -141,6 +175,8 @@ async function healthCheck() {
|
|
|
141
175
|
}
|
|
142
176
|
|
|
143
177
|
module.exports = {
|
|
178
|
+
createClient,
|
|
179
|
+
login,
|
|
144
180
|
register,
|
|
145
181
|
verifyEmail,
|
|
146
182
|
getUserInfo,
|
package/src/lib/config.js
CHANGED
|
@@ -51,12 +51,58 @@ function saveProjectConfig(config) {
|
|
|
51
51
|
}
|
|
52
52
|
|
|
53
53
|
/**
|
|
54
|
-
* Check if user is authenticated
|
|
54
|
+
* Check if user is authenticated and token is valid
|
|
55
55
|
*/
|
|
56
56
|
function isAuthenticated() {
|
|
57
|
-
const
|
|
58
|
-
|
|
59
|
-
|
|
57
|
+
const user = getUser();
|
|
58
|
+
|
|
59
|
+
if (!user || !user.email) {
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Check for session token
|
|
64
|
+
if (user.sessionToken) {
|
|
65
|
+
// Check if token is expired
|
|
66
|
+
if (user.expiresAt) {
|
|
67
|
+
const expirationDate = new Date(user.expiresAt);
|
|
68
|
+
const now = new Date();
|
|
69
|
+
|
|
70
|
+
if (now >= expirationDate) {
|
|
71
|
+
return false; // Token expired
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return true;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Fallback: Check for API key (for backward compatibility)
|
|
78
|
+
return !!user.apiKey;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Check if session token is expired
|
|
83
|
+
*/
|
|
84
|
+
function isTokenExpired() {
|
|
85
|
+
const user = getUser();
|
|
86
|
+
if (!user?.expiresAt) return false;
|
|
87
|
+
|
|
88
|
+
const expirationDate = new Date(user.expiresAt);
|
|
89
|
+
const now = new Date();
|
|
90
|
+
|
|
91
|
+
return now >= expirationDate;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Check if token expires soon (within 7 days)
|
|
96
|
+
*/
|
|
97
|
+
function isTokenExpiringSoon() {
|
|
98
|
+
const user = getUser();
|
|
99
|
+
if (!user?.expiresAt) return false;
|
|
100
|
+
|
|
101
|
+
const expirationDate = new Date(user.expiresAt);
|
|
102
|
+
const now = new Date();
|
|
103
|
+
const sevenDaysFromNow = new Date(now.getTime() + 7 * 24 * 60 * 60 * 1000);
|
|
104
|
+
|
|
105
|
+
return expirationDate <= sevenDaysFromNow && !isTokenExpired();
|
|
60
106
|
}
|
|
61
107
|
|
|
62
108
|
/**
|
|
@@ -99,6 +145,8 @@ module.exports = {
|
|
|
99
145
|
getProjectConfig,
|
|
100
146
|
saveProjectConfig,
|
|
101
147
|
isAuthenticated,
|
|
148
|
+
isTokenExpired,
|
|
149
|
+
isTokenExpiringSoon,
|
|
102
150
|
getUser,
|
|
103
151
|
saveUser,
|
|
104
152
|
clearUser,
|