coffeeinabit 0.0.22 → 0.0.25

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/cloud_auth.js CHANGED
@@ -1,170 +1,170 @@
1
- import axios from 'axios';
2
-
3
- export class CloudAuth {
4
- constructor() {
5
- this.sessionSecret = process.env.SESSION_SECRET || 'coffeeinabit-secret-key-change-in-production';
6
- }
7
-
8
- async getLoginUrl(redirectUri) {
9
- try {
10
- const params = new URLSearchParams({
11
- client_id: '4rhqg2pjfhjkq42mr7iilpnmsu',
12
- response_type: 'code',
13
- redirect_uri: redirectUri,
14
- scope: 'openid email profile'
15
- });
16
-
17
- const loginUrl = `https://coffeeinabit.auth.us-east-1.amazoncognito.com/login?${params.toString()}`;
18
-
19
- return {
20
- success: true,
21
- loginUrl: loginUrl
22
- };
23
- } catch (error) {
24
- return {
25
- success: false,
26
- error: error.message
27
- };
28
- }
29
- }
30
-
31
- async exchangeCodeForTokens(code, redirectUri) {
32
- try {
33
- console.log('[CloudAuth] Received authorization code:', code.substring(0, 10) + '...');
34
- console.log('[CloudAuth] Using redirect_uri:', redirectUri);
35
-
36
- const response = await axios.get(`https://api.coffeeinabit.com/auth/token?code=${encodeURIComponent(code)}&redirect_url=${encodeURIComponent(redirectUri)}`);
37
-
38
- if (response.data.error) {
39
- throw new Error(response.data.error);
40
- }
41
-
42
- const { id_token, access_token, refresh_token } = response.data;
43
-
44
- return {
45
- success: true,
46
- tokens: {
47
- idToken: id_token,
48
- accessToken: access_token,
49
- refreshToken: refresh_token
50
- },
51
- user: this.extractUserFromToken(id_token)
52
- };
53
- } catch (error) {
54
- console.error('[CloudAuth] Token exchange failed:', error.response?.status, error.response?.data);
55
- return {
56
- success: false,
57
- error: error.message
58
- };
59
- }
60
- }
61
-
62
- async refreshTokens(refreshToken) {
63
- try {
64
- const response = await axios.get(
65
- `https://api.coffeeinabit.com/auth/refresh?refresh_token=${encodeURIComponent(refreshToken)}`
66
- );
67
-
68
- if (response.data.error) {
69
- throw new Error(response.data.error);
70
- }
71
-
72
- return {
73
- success: true,
74
- tokens: {
75
- idToken: response.data.id_token,
76
- accessToken: response.data.access_token,
77
- refreshToken: response.data.refresh_token || refreshToken
78
- },
79
- user: this.extractUserFromToken(response.data.id_token)
80
- };
81
- } catch (error) {
82
- return {
83
- success: false,
84
- error: error.message
85
- };
86
- }
87
- }
88
-
89
- extractUserFromToken(idToken) {
90
- if (!idToken) return null;
91
-
92
- try {
93
- const payload = JSON.parse(Buffer.from(idToken.split('.')[1], 'base64').toString());
94
- return {
95
- email: payload.email || payload['cognito:username'],
96
- userId: payload.sub
97
- };
98
- } catch (error) {
99
- console.error('[CloudAuth] Error extracting user from token:', error.message);
100
- return null;
101
- }
102
- }
103
-
104
- isTokenExpired(token) {
105
- if (!token) return true;
106
-
107
- try {
108
- const payload = JSON.parse(Buffer.from(token.split('.')[1], 'base64').toString());
109
- if (!payload || !payload.exp) return true;
110
-
111
- const bufferTime = 5 * 60;
112
- return payload.exp < (Date.now() / 1000) + bufferTime;
113
- } catch (error) {
114
- console.error('[CloudAuth] Error checking token expiration:', error.message);
115
- return true;
116
- }
117
- }
118
-
119
- async ensureValidToken(session) {
120
- if (!session.tokens?.idToken) {
121
- return { valid: false, error: 'No token available' };
122
- }
123
-
124
- if (this.isTokenExpired(session.tokens.idToken)) {
125
- console.log('[CloudAuth] Token expired or expiring soon, attempting refresh...');
126
-
127
- if (!session.tokens.refreshToken) {
128
- return { valid: false, error: 'No refresh token available' };
129
- }
130
-
131
- const result = await this.refreshTokens(session.tokens.refreshToken);
132
-
133
- if (result.success) {
134
- this.storeTokensInSession(session, result.tokens, result.user);
135
- return { valid: true };
136
- } else {
137
- return { valid: false, error: result.error };
138
- }
139
- }
140
-
141
- return { valid: true };
142
- }
143
-
144
- storeTokensInSession(session, tokens, user) {
145
- session.tokens = tokens;
146
- session.user = user;
147
- console.log('[CloudAuth] Tokens stored in session for user:', user?.email);
148
- }
149
-
150
- clearSession(session) {
151
- session.tokens = null;
152
- session.user = null;
153
- console.log('[CloudAuth] Session cleared');
154
- }
155
-
156
- isAuthenticated(session) {
157
- return !!(session.tokens && session.tokens.idToken && session.tokens.accessToken);
158
- }
159
-
160
- getAuthStatus(session) {
161
- if (this.isAuthenticated(session)) {
162
- return {
163
- authenticated: true,
164
- user: session.user || null
165
- };
166
- } else {
167
- return { authenticated: false };
168
- }
169
- }
170
- }
1
+ import axios from 'axios';
2
+
3
+ export class CloudAuth {
4
+ constructor() {
5
+ this.sessionSecret = process.env.SESSION_SECRET || 'coffeeinabit-secret-key-change-in-production';
6
+ }
7
+
8
+ async getLoginUrl(redirectUri) {
9
+ try {
10
+ const params = new URLSearchParams({
11
+ client_id: '4rhqg2pjfhjkq42mr7iilpnmsu',
12
+ response_type: 'code',
13
+ redirect_uri: redirectUri,
14
+ scope: 'openid email profile'
15
+ });
16
+
17
+ const loginUrl = `https://coffeeinabit.auth.us-east-1.amazoncognito.com/login?${params.toString()}`;
18
+
19
+ return {
20
+ success: true,
21
+ loginUrl: loginUrl
22
+ };
23
+ } catch (error) {
24
+ return {
25
+ success: false,
26
+ error: error.message
27
+ };
28
+ }
29
+ }
30
+
31
+ async exchangeCodeForTokens(code, redirectUri) {
32
+ try {
33
+ console.log('[CloudAuth] Received authorization code:', code.substring(0, 10) + '...');
34
+ console.log('[CloudAuth] Using redirect_uri:', redirectUri);
35
+
36
+ const response = await axios.get(`https://api.coffeeinabit.com/auth/token?code=${encodeURIComponent(code)}&redirect_url=${encodeURIComponent(redirectUri)}`);
37
+
38
+ if (response.data.error) {
39
+ throw new Error(response.data.error);
40
+ }
41
+
42
+ const { id_token, access_token, refresh_token } = response.data;
43
+
44
+ return {
45
+ success: true,
46
+ tokens: {
47
+ idToken: id_token,
48
+ accessToken: access_token,
49
+ refreshToken: refresh_token
50
+ },
51
+ user: this.extractUserFromToken(id_token)
52
+ };
53
+ } catch (error) {
54
+ console.error('[CloudAuth] Token exchange failed:', error.response?.status, error.response?.data);
55
+ return {
56
+ success: false,
57
+ error: error.message
58
+ };
59
+ }
60
+ }
61
+
62
+ async refreshTokens(refreshToken) {
63
+ try {
64
+ const response = await axios.get(
65
+ `https://api.coffeeinabit.com/auth/refresh?refresh_token=${encodeURIComponent(refreshToken)}`
66
+ );
67
+
68
+ if (response.data.error) {
69
+ throw new Error(response.data.error);
70
+ }
71
+
72
+ return {
73
+ success: true,
74
+ tokens: {
75
+ idToken: response.data.id_token,
76
+ accessToken: response.data.access_token,
77
+ refreshToken: response.data.refresh_token || refreshToken
78
+ },
79
+ user: this.extractUserFromToken(response.data.id_token)
80
+ };
81
+ } catch (error) {
82
+ return {
83
+ success: false,
84
+ error: error.message
85
+ };
86
+ }
87
+ }
88
+
89
+ extractUserFromToken(idToken) {
90
+ if (!idToken) return null;
91
+
92
+ try {
93
+ const payload = JSON.parse(Buffer.from(idToken.split('.')[1], 'base64').toString());
94
+ return {
95
+ email: payload.email || payload['cognito:username'],
96
+ userId: payload.sub
97
+ };
98
+ } catch (error) {
99
+ console.error('[CloudAuth] Error extracting user from token:', error.message);
100
+ return null;
101
+ }
102
+ }
103
+
104
+ isTokenExpired(token) {
105
+ if (!token) return true;
106
+
107
+ try {
108
+ const payload = JSON.parse(Buffer.from(token.split('.')[1], 'base64').toString());
109
+ if (!payload || !payload.exp) return true;
110
+
111
+ const bufferTime = 5 * 60;
112
+ return payload.exp < (Date.now() / 1000) + bufferTime;
113
+ } catch (error) {
114
+ console.error('[CloudAuth] Error checking token expiration:', error.message);
115
+ return true;
116
+ }
117
+ }
118
+
119
+ async ensureValidToken(session) {
120
+ if (!session.tokens?.idToken) {
121
+ return { valid: false, error: 'No token available' };
122
+ }
123
+
124
+ if (this.isTokenExpired(session.tokens.idToken)) {
125
+ console.log('[CloudAuth] Token expired or expiring soon, attempting refresh...');
126
+
127
+ if (!session.tokens.refreshToken) {
128
+ return { valid: false, error: 'No refresh token available' };
129
+ }
130
+
131
+ const result = await this.refreshTokens(session.tokens.refreshToken);
132
+
133
+ if (result.success) {
134
+ this.storeTokensInSession(session, result.tokens, result.user);
135
+ return { valid: true };
136
+ } else {
137
+ return { valid: false, error: result.error };
138
+ }
139
+ }
140
+
141
+ return { valid: true };
142
+ }
143
+
144
+ storeTokensInSession(session, tokens, user) {
145
+ session.tokens = tokens;
146
+ session.user = user;
147
+ console.log('[CloudAuth] Tokens stored in session for user:', user?.email);
148
+ }
149
+
150
+ clearSession(session) {
151
+ session.tokens = null;
152
+ session.user = null;
153
+ console.log('[CloudAuth] Session cleared');
154
+ }
155
+
156
+ isAuthenticated(session) {
157
+ return !!(session.tokens && session.tokens.idToken && session.tokens.accessToken);
158
+ }
159
+
160
+ getAuthStatus(session) {
161
+ if (this.isAuthenticated(session)) {
162
+ return {
163
+ authenticated: true,
164
+ user: session.user || null
165
+ };
166
+ } else {
167
+ return { authenticated: false };
168
+ }
169
+ }
170
+ }
package/package.json CHANGED
@@ -1,34 +1,35 @@
1
- {
2
- "name": "coffeeinabit",
3
- "version": "0.0.22",
4
- "description": "CoffeeInABit App",
5
- "main": "server.js",
6
- "type": "module",
7
- "keywords": [
8
- "automation",
9
- "linkedin",
10
- "windows",
11
- "playwright"
12
- ],
13
- "author": "Azam Alamov <azam.alamov@icloud.com>",
14
- "license": "MIT",
15
- "bin": {
16
- "coffeeinabit": "./server.js"
17
- },
18
- "scripts": {
19
- "start": "node server.js",
20
- "dev": "nodemon server.js"
21
- },
22
- "dependencies": {
23
- "axios": "^1.6.0",
24
- "dotenv": "^16.3.1",
25
- "express": "^4.18.2",
26
- "express-session": "^1.17.3",
27
- "playwright": "^1.40.0",
28
- "session-file-store": "^1.5.0",
29
- "socket.io": "^4.7.5"
30
- },
31
- "devDependencies": {
32
- "nodemon": "^3.0.1"
33
- }
34
- }
1
+ {
2
+ "name": "coffeeinabit",
3
+ "version": "0.0.25",
4
+ "description": "CoffeeInABit App",
5
+ "main": "server.js",
6
+ "type": "module",
7
+ "keywords": [
8
+ "automation",
9
+ "linkedin",
10
+ "windows",
11
+ "playwright"
12
+ ],
13
+ "author": "Azam Alamov <azam.alamov@icloud.com>",
14
+ "license": "MIT",
15
+ "bin": {
16
+ "coffeeinabit": "./server.js"
17
+ },
18
+ "scripts": {
19
+ "start": "node server.js",
20
+ "dev": "nodemon server.js",
21
+ "postinstall": "npx playwright install firefox"
22
+ },
23
+ "dependencies": {
24
+ "axios": "^1.6.0",
25
+ "dotenv": "^16.3.1",
26
+ "express": "^4.18.2",
27
+ "express-session": "^1.17.3",
28
+ "playwright": "^1.40.0",
29
+ "session-file-store": "^1.5.0",
30
+ "socket.io": "^4.7.5"
31
+ },
32
+ "devDependencies": {
33
+ "nodemon": "^3.0.1"
34
+ }
35
+ }
package/public/login.html CHANGED
@@ -1,126 +1,126 @@
1
- <!DOCTYPE html>
2
- <html lang="en">
3
- <head>
4
- <meta charset="UTF-8">
5
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>CoffeeInABit - Login</title>
7
- <link rel="icon" type="image/png" href="/images/favicon.png">
8
- <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
9
- <link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.0/font/bootstrap-icons.css" rel="stylesheet">
10
- <link href="/styles.css" rel="stylesheet">
11
- </head>
12
- <body>
13
- <div class="container-fluid vh-100 d-flex align-items-center justify-content-center">
14
- <div class="row w-100 justify-content-center">
15
- <div class="col-md-6 col-lg-4">
16
- <div class="glass-card">
17
- <div class="text-center mb-4">
18
- <h1 class="h3 mb-3 fw-bold" style="color: #ffffff;">
19
- <img src="/images/fav_clear.png" alt="CoffeeInABit" style="width: 75px; height: 75px; margin-bottom: 8px; vertical-align: middle;">
20
- CoffeeInABit
21
- </h1>
22
- <p class="text-muted">Sign in to your account to continue</p>
23
- </div>
24
-
25
- <div class="d-grid gap-2">
26
- <button id="loginButton" class="glass-button btn-primary" onclick="loginToCloud()">
27
- <i class="bi bi-cloud-arrow-up me-2"></i>
28
- Login to CoffeeInABit Cloud
29
- </button>
30
- </div>
31
- </div>
32
- </div>
33
- </div>
34
- </div>
35
-
36
- <div id="toastContainer" class="toast-container"></div>
37
-
38
- <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
39
- <script>
40
- const loginButton = document.getElementById('loginButton');
41
- const toastContainer = document.getElementById('toastContainer');
42
-
43
- async function checkAuthStatus() {
44
- try {
45
- const response = await fetch('/auth/status');
46
- const data = await response.json();
47
-
48
- if (data.authenticated) {
49
- window.location.href = '/dashboard';
50
- }
51
- } catch (error) {
52
- console.error('Error checking auth status:', error);
53
- }
54
- }
55
-
56
- function loginToCloud() {
57
- loginButton.disabled = true;
58
- loginButton.innerHTML = '<span class="spinner-border spinner-border-sm me-2" role="status"></span>Redirecting...';
59
- window.location.href = '/auth/login';
60
- }
61
-
62
- function showAlert(type, message) {
63
- const icons = {
64
- success: 'bi-check-circle-fill',
65
- error: 'bi-exclamation-circle-fill',
66
- info: 'bi-info-circle-fill',
67
- warning: 'bi-exclamation-triangle-fill'
68
- };
69
-
70
- const titles = {
71
- success: 'Success',
72
- error: 'Error',
73
- info: 'Info',
74
- warning: 'Warning'
75
- };
76
-
77
- const toastId = 'toast-' + Date.now();
78
- const toast = document.createElement('div');
79
- toast.className = `toast-notification ${type}`;
80
- toast.id = toastId;
81
- toast.innerHTML = `
82
- <i class="bi ${icons[type] || icons.info} toast-icon"></i>
83
- <div class="toast-content">
84
- <strong>${titles[type] || titles.info}</strong>
85
- <p>${message}</p>
86
- </div>
87
- <button class="toast-close" onclick="removeToast('${toastId}')">
88
- <i class="bi bi-x"></i>
89
- </button>
90
- `;
91
-
92
- toastContainer.appendChild(toast);
93
-
94
- setTimeout(() => {
95
- removeToast(toastId);
96
- }, 6000);
97
- }
98
-
99
- function removeToast(toastId) {
100
- const toast = document.getElementById(toastId);
101
- if (toast) {
102
- toast.classList.add('removing');
103
- setTimeout(() => {
104
- toast.remove();
105
- }, 300);
106
- }
107
- }
108
-
109
- const urlParams = new URLSearchParams(window.location.search);
110
- const loginStatus = urlParams.get('login');
111
- const errorMessage = urlParams.get('error');
112
-
113
- if (loginStatus === 'success') {
114
- showAlert('success', 'Successfully logged in! Redirecting to dashboard...');
115
- setTimeout(() => {
116
- window.location.href = '/dashboard';
117
- }, 2000);
118
- } else if (errorMessage) {
119
- showAlert('error', `Login failed: ${decodeURIComponent(errorMessage)}`);
120
- history.replaceState({}, document.title, window.location.pathname);
121
- }
122
-
123
- checkAuthStatus();
124
- </script>
125
- </body>
126
- </html>
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>CoffeeInABit - Login</title>
7
+ <link rel="icon" type="image/png" href="/images/favicon.png">
8
+ <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
9
+ <link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.0/font/bootstrap-icons.css" rel="stylesheet">
10
+ <link href="/styles.css" rel="stylesheet">
11
+ </head>
12
+ <body>
13
+ <div class="container-fluid vh-100 d-flex align-items-center justify-content-center">
14
+ <div class="row w-100 justify-content-center">
15
+ <div class="col-md-6 col-lg-4">
16
+ <div class="glass-card">
17
+ <div class="text-center mb-4">
18
+ <h1 class="h3 mb-3 fw-bold" style="color: #ffffff;">
19
+ <img src="/images/fav_clear.png" alt="CoffeeInABit" style="width: 75px; height: 75px; margin-bottom: 8px; vertical-align: middle;">
20
+ CoffeeInABit
21
+ </h1>
22
+ <p class="text-muted">Sign in to your account to continue</p>
23
+ </div>
24
+
25
+ <div class="d-grid gap-2">
26
+ <button id="loginButton" class="glass-button btn-primary" onclick="loginToCloud()">
27
+ <i class="bi bi-cloud-arrow-up me-2"></i>
28
+ Login to CoffeeInABit Cloud
29
+ </button>
30
+ </div>
31
+ </div>
32
+ </div>
33
+ </div>
34
+ </div>
35
+
36
+ <div id="toastContainer" class="toast-container"></div>
37
+
38
+ <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
39
+ <script>
40
+ const loginButton = document.getElementById('loginButton');
41
+ const toastContainer = document.getElementById('toastContainer');
42
+
43
+ async function checkAuthStatus() {
44
+ try {
45
+ const response = await fetch('/auth/status');
46
+ const data = await response.json();
47
+
48
+ if (data.authenticated) {
49
+ window.location.href = '/dashboard';
50
+ }
51
+ } catch (error) {
52
+ console.error('Error checking auth status:', error);
53
+ }
54
+ }
55
+
56
+ function loginToCloud() {
57
+ loginButton.disabled = true;
58
+ loginButton.innerHTML = '<span class="spinner-border spinner-border-sm me-2" role="status"></span>Redirecting...';
59
+ window.location.href = '/auth/login';
60
+ }
61
+
62
+ function showAlert(type, message) {
63
+ const icons = {
64
+ success: 'bi-check-circle-fill',
65
+ error: 'bi-exclamation-circle-fill',
66
+ info: 'bi-info-circle-fill',
67
+ warning: 'bi-exclamation-triangle-fill'
68
+ };
69
+
70
+ const titles = {
71
+ success: 'Success',
72
+ error: 'Error',
73
+ info: 'Info',
74
+ warning: 'Warning'
75
+ };
76
+
77
+ const toastId = 'toast-' + Date.now();
78
+ const toast = document.createElement('div');
79
+ toast.className = `toast-notification ${type}`;
80
+ toast.id = toastId;
81
+ toast.innerHTML = `
82
+ <i class="bi ${icons[type] || icons.info} toast-icon"></i>
83
+ <div class="toast-content">
84
+ <strong>${titles[type] || titles.info}</strong>
85
+ <p>${message}</p>
86
+ </div>
87
+ <button class="toast-close" onclick="removeToast('${toastId}')">
88
+ <i class="bi bi-x"></i>
89
+ </button>
90
+ `;
91
+
92
+ toastContainer.appendChild(toast);
93
+
94
+ setTimeout(() => {
95
+ removeToast(toastId);
96
+ }, 6000);
97
+ }
98
+
99
+ function removeToast(toastId) {
100
+ const toast = document.getElementById(toastId);
101
+ if (toast) {
102
+ toast.classList.add('removing');
103
+ setTimeout(() => {
104
+ toast.remove();
105
+ }, 300);
106
+ }
107
+ }
108
+
109
+ const urlParams = new URLSearchParams(window.location.search);
110
+ const loginStatus = urlParams.get('login');
111
+ const errorMessage = urlParams.get('error');
112
+
113
+ if (loginStatus === 'success') {
114
+ showAlert('success', 'Successfully logged in! Redirecting to dashboard...');
115
+ setTimeout(() => {
116
+ window.location.href = '/dashboard';
117
+ }, 2000);
118
+ } else if (errorMessage) {
119
+ showAlert('error', `Login failed: ${decodeURIComponent(errorMessage)}`);
120
+ history.replaceState({}, document.title, window.location.pathname);
121
+ }
122
+
123
+ checkAuthStatus();
124
+ </script>
125
+ </body>
126
+ </html>