crewly 1.11.4 → 1.11.6
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/dist/backend/backend/src/constants.d.ts +22 -1
- package/dist/backend/backend/src/constants.d.ts.map +1 -1
- package/dist/backend/backend/src/constants.js +22 -1
- package/dist/backend/backend/src/constants.js.map +1 -1
- package/dist/backend/backend/src/services/backup/backup-archive.service.d.ts +90 -0
- package/dist/backend/backend/src/services/backup/backup-archive.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/backup/backup-archive.service.js +309 -0
- package/dist/backend/backend/src/services/backup/backup-archive.service.js.map +1 -0
- package/dist/backend/backend/src/services/backup/backup-cloud.client.d.ts +75 -0
- package/dist/backend/backend/src/services/backup/backup-cloud.client.d.ts.map +1 -0
- package/dist/backend/backend/src/services/backup/backup-cloud.client.js +134 -0
- package/dist/backend/backend/src/services/backup/backup-cloud.client.js.map +1 -0
- package/dist/backend/backend/src/services/backup/backup-restore.service.d.ts +78 -0
- package/dist/backend/backend/src/services/backup/backup-restore.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/backup/backup-restore.service.js +358 -0
- package/dist/backend/backend/src/services/backup/backup-restore.service.js.map +1 -0
- package/dist/backend/backend/src/services/backup/backup.types.d.ts +163 -0
- package/dist/backend/backend/src/services/backup/backup.types.d.ts.map +1 -0
- package/dist/backend/backend/src/services/backup/backup.types.js +13 -0
- package/dist/backend/backend/src/services/backup/backup.types.js.map +1 -0
- package/dist/backend/backend/src/services/cloud/cloud-sync.service.d.ts +29 -2
- package/dist/backend/backend/src/services/cloud/cloud-sync.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/cloud/cloud-sync.service.js +97 -13
- package/dist/backend/backend/src/services/cloud/cloud-sync.service.js.map +1 -1
- package/dist/cli/backend/src/constants.d.ts +22 -1
- package/dist/cli/backend/src/constants.d.ts.map +1 -1
- package/dist/cli/backend/src/constants.js +22 -1
- package/dist/cli/backend/src/constants.js.map +1 -1
- package/dist/cli/backend/src/controllers/cloud/cloud-google-auth.controller.d.ts +70 -0
- package/dist/cli/backend/src/controllers/cloud/cloud-google-auth.controller.d.ts.map +1 -0
- package/dist/cli/backend/src/controllers/cloud/cloud-google-auth.controller.js +427 -0
- package/dist/cli/backend/src/controllers/cloud/cloud-google-auth.controller.js.map +1 -0
- package/dist/cli/backend/src/services/backup/backup-archive.service.d.ts +90 -0
- package/dist/cli/backend/src/services/backup/backup-archive.service.d.ts.map +1 -0
- package/dist/cli/backend/src/services/backup/backup-archive.service.js +309 -0
- package/dist/cli/backend/src/services/backup/backup-archive.service.js.map +1 -0
- package/dist/cli/backend/src/services/backup/backup-cloud.client.d.ts +75 -0
- package/dist/cli/backend/src/services/backup/backup-cloud.client.d.ts.map +1 -0
- package/dist/cli/backend/src/services/backup/backup-cloud.client.js +134 -0
- package/dist/cli/backend/src/services/backup/backup-cloud.client.js.map +1 -0
- package/dist/cli/backend/src/services/backup/backup-restore.service.d.ts +78 -0
- package/dist/cli/backend/src/services/backup/backup-restore.service.d.ts.map +1 -0
- package/dist/cli/backend/src/services/backup/backup-restore.service.js +358 -0
- package/dist/cli/backend/src/services/backup/backup-restore.service.js.map +1 -0
- package/dist/cli/backend/src/services/backup/backup.types.d.ts +163 -0
- package/dist/cli/backend/src/services/backup/backup.types.d.ts.map +1 -0
- package/dist/cli/backend/src/services/backup/backup.types.js +13 -0
- package/dist/cli/backend/src/services/backup/backup.types.js.map +1 -0
- package/dist/cli/backend/src/services/cloud/cloud-client.service.d.ts +410 -0
- package/dist/cli/backend/src/services/cloud/cloud-client.service.d.ts.map +1 -0
- package/dist/cli/backend/src/services/cloud/cloud-client.service.js +863 -0
- package/dist/cli/backend/src/services/cloud/cloud-client.service.js.map +1 -0
- package/dist/cli/backend/src/services/cloud/cloud-sync.service.d.ts +292 -0
- package/dist/cli/backend/src/services/cloud/cloud-sync.service.d.ts.map +1 -0
- package/dist/cli/backend/src/services/cloud/cloud-sync.service.js +1093 -0
- package/dist/cli/backend/src/services/cloud/cloud-sync.service.js.map +1 -0
- package/dist/cli/backend/src/services/cloud/cloud-sync.types.d.ts +328 -0
- package/dist/cli/backend/src/services/cloud/cloud-sync.types.d.ts.map +1 -0
- package/dist/cli/backend/src/services/cloud/cloud-sync.types.js +171 -0
- package/dist/cli/backend/src/services/cloud/cloud-sync.types.js.map +1 -0
- package/dist/cli/backend/src/services/cloud/device-identity.service.d.ts +89 -0
- package/dist/cli/backend/src/services/cloud/device-identity.service.d.ts.map +1 -0
- package/dist/cli/backend/src/services/cloud/device-identity.service.js +148 -0
- package/dist/cli/backend/src/services/cloud/device-identity.service.js.map +1 -0
- package/dist/cli/backend/src/services/user/user-identity.service.d.ts +86 -0
- package/dist/cli/backend/src/services/user/user-identity.service.d.ts.map +1 -0
- package/dist/cli/backend/src/services/user/user-identity.service.js +190 -0
- package/dist/cli/backend/src/services/user/user-identity.service.js.map +1 -0
- package/dist/cli/cli/src/commands/backup.d.ts +31 -0
- package/dist/cli/cli/src/commands/backup.d.ts.map +1 -0
- package/dist/cli/cli/src/commands/backup.js +280 -0
- package/dist/cli/cli/src/commands/backup.js.map +1 -0
- package/dist/cli/cli/src/index.js +10 -0
- package/dist/cli/cli/src/index.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,427 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cloud Google OAuth Controller
|
|
3
|
+
*
|
|
4
|
+
* Handles Google OAuth login flow for the CrewlyAI Cloud Console.
|
|
5
|
+
* Provides both browser-redirect (GET) and API (POST) flows.
|
|
6
|
+
*
|
|
7
|
+
* Routes:
|
|
8
|
+
* - GET /api/cloud/google/start -> Redirects to Google consent screen
|
|
9
|
+
* - GET /api/cloud/google/callback -> Handles Google redirect, issues JWT, redirects to frontend
|
|
10
|
+
* - POST /api/cloud/google/url -> Returns Google OAuth URL as JSON (for SPA clients)
|
|
11
|
+
* - POST /api/cloud/google/callback -> Exchanges code for JWT, returns JSON (for SPA clients)
|
|
12
|
+
*
|
|
13
|
+
* @module controllers/cloud/cloud-google-auth.controller
|
|
14
|
+
*/
|
|
15
|
+
import crypto from 'crypto';
|
|
16
|
+
import { GOOGLE_OAUTH_CONSTANTS, AUTH_CONSTANTS, CLOUD_AUTH_CONSTANTS } from '../../constants.js';
|
|
17
|
+
import { UserIdentityService } from '../../services/user/user-identity.service.js';
|
|
18
|
+
import { DeviceIdentityService } from '../../services/cloud/device-identity.service.js';
|
|
19
|
+
import { CloudClientService } from '../../services/cloud/cloud-client.service.js';
|
|
20
|
+
import { LoggerService } from '../../services/core/logger.service.js';
|
|
21
|
+
const logger = LoggerService.getInstance().createComponentLogger('CloudGoogleAuth');
|
|
22
|
+
/** Env var for the Cloud Console frontend URL (where to redirect after login). */
|
|
23
|
+
const CLOUD_CONSOLE_FRONTEND_URL = () => process.env['CLOUD_CONSOLE_URL'] || process.env['CLOUD_PORTAL_URL'] || 'https://crewlyai.com';
|
|
24
|
+
/** Env var for the Google OAuth redirect URI (must match GCP console). */
|
|
25
|
+
const CLOUD_GOOGLE_REDIRECT_URI = (req) => process.env['CLOUD_GOOGLE_REDIRECT_URI'] ||
|
|
26
|
+
`${req.protocol}://${req.get('host')}/api/cloud/google/callback`;
|
|
27
|
+
/** Scopes for Cloud Console login -- only need email and profile. */
|
|
28
|
+
const LOGIN_SCOPES = ['openid', 'email', 'profile'];
|
|
29
|
+
/** Default user plan assigned to new users. */
|
|
30
|
+
const DEFAULT_USER_PLAN = AUTH_CONSTANTS.PLANS.FREE;
|
|
31
|
+
/**
|
|
32
|
+
* Build a Google OAuth consent URL with the given post-login redirect.
|
|
33
|
+
*
|
|
34
|
+
* Consolidates URL construction used by both cloudGoogleStart and cloudGoogleUrl.
|
|
35
|
+
*
|
|
36
|
+
* @param req - Express request (used to derive redirect_uri)
|
|
37
|
+
* @param postLoginRedirect - Where to send user after login completes
|
|
38
|
+
* @returns Full Google OAuth consent URL string
|
|
39
|
+
* @throws Error if GOOGLE_CLIENT_ID is not configured
|
|
40
|
+
*/
|
|
41
|
+
function buildGoogleOAuthUrl(req, postLoginRedirect) {
|
|
42
|
+
const clientId = CLOUD_AUTH_CONSTANTS.GOOGLE.CLIENT_ID;
|
|
43
|
+
if (!clientId)
|
|
44
|
+
throw new Error('GOOGLE_CLIENT_ID is not configured');
|
|
45
|
+
const redirectUri = CLOUD_GOOGLE_REDIRECT_URI(req);
|
|
46
|
+
const statePayload = {
|
|
47
|
+
redirectTo: postLoginRedirect,
|
|
48
|
+
t: Date.now(),
|
|
49
|
+
nonce: crypto.randomUUID(),
|
|
50
|
+
};
|
|
51
|
+
const state = Buffer.from(JSON.stringify(statePayload)).toString('base64url');
|
|
52
|
+
const url = new URL(GOOGLE_OAUTH_CONSTANTS.AUTH_BASE_URL);
|
|
53
|
+
url.searchParams.set('client_id', clientId);
|
|
54
|
+
url.searchParams.set('redirect_uri', redirectUri);
|
|
55
|
+
url.searchParams.set('response_type', 'code');
|
|
56
|
+
url.searchParams.set('access_type', 'offline');
|
|
57
|
+
url.searchParams.set('prompt', 'consent');
|
|
58
|
+
url.searchParams.set('scope', LOGIN_SCOPES.join(' '));
|
|
59
|
+
url.searchParams.set('state', state);
|
|
60
|
+
return url.toString();
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Exchange a Google authorization code for tokens, fetch user profile,
|
|
64
|
+
* and upsert the user in the local identity store.
|
|
65
|
+
*
|
|
66
|
+
* Shared by both GET and POST callback handlers.
|
|
67
|
+
*
|
|
68
|
+
* @param code - Google authorization code
|
|
69
|
+
* @param req - Express request (used to derive redirect_uri)
|
|
70
|
+
* @returns GoogleLoginResult with user, profile, and token data
|
|
71
|
+
* @throws Error on credential misconfiguration, token exchange failure, or missing profile data
|
|
72
|
+
*/
|
|
73
|
+
async function exchangeCodeAndCreateUser(code, req) {
|
|
74
|
+
const clientId = CLOUD_AUTH_CONSTANTS.GOOGLE.CLIENT_ID;
|
|
75
|
+
const clientSecret = process.env['GOOGLE_CLIENT_SECRET'] || '';
|
|
76
|
+
if (!clientId || !clientSecret) {
|
|
77
|
+
throw new Error('Google OAuth credentials not configured');
|
|
78
|
+
}
|
|
79
|
+
const redirectUri = CLOUD_GOOGLE_REDIRECT_URI(req);
|
|
80
|
+
// Exchange code for tokens
|
|
81
|
+
const tokenResp = await fetch(GOOGLE_OAUTH_CONSTANTS.TOKEN_ENDPOINT, {
|
|
82
|
+
method: 'POST',
|
|
83
|
+
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
84
|
+
body: new URLSearchParams({
|
|
85
|
+
code,
|
|
86
|
+
client_id: clientId,
|
|
87
|
+
client_secret: clientSecret,
|
|
88
|
+
redirect_uri: redirectUri,
|
|
89
|
+
grant_type: 'authorization_code',
|
|
90
|
+
}),
|
|
91
|
+
});
|
|
92
|
+
if (!tokenResp.ok) {
|
|
93
|
+
const details = await tokenResp.text();
|
|
94
|
+
logger.error('Failed to exchange Google OAuth code', { status: tokenResp.status, details });
|
|
95
|
+
throw new Error(`token_exchange_failed: ${tokenResp.status}`);
|
|
96
|
+
}
|
|
97
|
+
const tokenData = (await tokenResp.json());
|
|
98
|
+
// Fetch Google profile
|
|
99
|
+
const profileResp = await fetch(GOOGLE_OAUTH_CONSTANTS.USERINFO_ENDPOINT, {
|
|
100
|
+
headers: { Authorization: `Bearer ${tokenData.access_token}` },
|
|
101
|
+
});
|
|
102
|
+
if (!profileResp.ok) {
|
|
103
|
+
logger.error('Failed to fetch Google profile', { status: profileResp.status });
|
|
104
|
+
throw new Error(`profile_fetch_failed: ${profileResp.status}`);
|
|
105
|
+
}
|
|
106
|
+
const profile = (await profileResp.json());
|
|
107
|
+
if (!profile.email) {
|
|
108
|
+
throw new Error('no_email');
|
|
109
|
+
}
|
|
110
|
+
// Create or find user and store tokens
|
|
111
|
+
const users = UserIdentityService.getInstance();
|
|
112
|
+
const user = await users.createOrUpdateUser({ email: profile.email });
|
|
113
|
+
if (tokenData.refresh_token || tokenData.access_token) {
|
|
114
|
+
await users.connectService(user.id, 'google', {
|
|
115
|
+
refreshToken: tokenData.refresh_token || tokenData.access_token,
|
|
116
|
+
accessToken: tokenData.access_token,
|
|
117
|
+
scopes: LOGIN_SCOPES,
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
return {
|
|
121
|
+
user,
|
|
122
|
+
profile: { email: profile.email, name: profile.name, picture: profile.picture },
|
|
123
|
+
tokenData: { access_token: tokenData.access_token, refresh_token: tokenData.refresh_token },
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Sign a JWT using HMAC-SHA256.
|
|
128
|
+
*
|
|
129
|
+
* @param payload - JWT payload object
|
|
130
|
+
* @returns Signed JWT string (header.payload.signature)
|
|
131
|
+
*/
|
|
132
|
+
export function signJwt(payload) {
|
|
133
|
+
const header = { alg: 'HS256', typ: 'JWT' };
|
|
134
|
+
const headerB64 = Buffer.from(JSON.stringify(header)).toString('base64url');
|
|
135
|
+
const payloadB64 = Buffer.from(JSON.stringify(payload)).toString('base64url');
|
|
136
|
+
const signature = crypto
|
|
137
|
+
.createHmac('sha256', AUTH_CONSTANTS.JWT.DEFAULT_SECRET)
|
|
138
|
+
.update(`${headerB64}.${payloadB64}`)
|
|
139
|
+
.digest('base64url');
|
|
140
|
+
return `${headerB64}.${payloadB64}.${signature}`;
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Verify a JWT signed with HMAC-SHA256.
|
|
144
|
+
*
|
|
145
|
+
* @param token - JWT string (header.payload.signature)
|
|
146
|
+
* @returns Decoded payload if valid, null if invalid or expired
|
|
147
|
+
*/
|
|
148
|
+
export function verifyJwt(token) {
|
|
149
|
+
try {
|
|
150
|
+
const parts = token.split('.');
|
|
151
|
+
if (parts.length !== 3)
|
|
152
|
+
return null;
|
|
153
|
+
const [headerB64, payloadB64, signature] = parts;
|
|
154
|
+
const expectedSig = crypto
|
|
155
|
+
.createHmac('sha256', AUTH_CONSTANTS.JWT.DEFAULT_SECRET)
|
|
156
|
+
.update(`${headerB64}.${payloadB64}`)
|
|
157
|
+
.digest('base64url');
|
|
158
|
+
if (signature !== expectedSig)
|
|
159
|
+
return null;
|
|
160
|
+
const payload = JSON.parse(Buffer.from(payloadB64, 'base64url').toString('utf8'));
|
|
161
|
+
// Check expiry
|
|
162
|
+
if (payload.exp && payload.exp < Math.floor(Date.now() / 1000)) {
|
|
163
|
+
return null;
|
|
164
|
+
}
|
|
165
|
+
return payload;
|
|
166
|
+
}
|
|
167
|
+
catch {
|
|
168
|
+
return null;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
// ---------------------------------------------------------------------------
|
|
172
|
+
// Route handlers
|
|
173
|
+
// ---------------------------------------------------------------------------
|
|
174
|
+
/**
|
|
175
|
+
* GET /api/cloud/google/start
|
|
176
|
+
*
|
|
177
|
+
* Redirects the browser to the Google OAuth consent screen.
|
|
178
|
+
*
|
|
179
|
+
* @param req - Express request with optional query: { redirect }
|
|
180
|
+
* @param res - Express response (302 redirect)
|
|
181
|
+
* @param next - Next function for error propagation
|
|
182
|
+
*/
|
|
183
|
+
export async function cloudGoogleStart(req, res, next) {
|
|
184
|
+
try {
|
|
185
|
+
const postLoginRedirect = req.query['redirect'] ? String(req.query['redirect']) : '';
|
|
186
|
+
const url = buildGoogleOAuthUrl(req, postLoginRedirect);
|
|
187
|
+
logger.info('Redirecting to Google OAuth consent screen');
|
|
188
|
+
res.redirect(url);
|
|
189
|
+
}
|
|
190
|
+
catch (error) {
|
|
191
|
+
if (error instanceof Error && error.message.includes('GOOGLE_CLIENT_ID')) {
|
|
192
|
+
res.status(500).json({ success: false, error: error.message });
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
logger.error('Failed to initiate Google OAuth', {
|
|
196
|
+
error: error instanceof Error ? error.message : String(error),
|
|
197
|
+
});
|
|
198
|
+
next(error);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* GET /api/cloud/google/callback
|
|
203
|
+
*
|
|
204
|
+
* Handles the Google OAuth redirect, exchanges code, issues JWT, and redirects.
|
|
205
|
+
*
|
|
206
|
+
* @param req - Express request with query: { code, state? }
|
|
207
|
+
* @param res - Express response (302 redirect to frontend)
|
|
208
|
+
* @param next - Next function for error propagation
|
|
209
|
+
*/
|
|
210
|
+
export async function cloudGoogleCallback(req, res, next) {
|
|
211
|
+
try {
|
|
212
|
+
const code = req.query['code'] ? String(req.query['code']) : '';
|
|
213
|
+
const state = req.query['state'] ? String(req.query['state']) : '';
|
|
214
|
+
const errorParam = req.query['error'] ? String(req.query['error']) : '';
|
|
215
|
+
const consoleUrl = CLOUD_CONSOLE_FRONTEND_URL();
|
|
216
|
+
if (errorParam) {
|
|
217
|
+
logger.warn('Google OAuth returned error', { error: errorParam });
|
|
218
|
+
res.redirect(`${consoleUrl}/login?error=${encodeURIComponent(errorParam)}`);
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
if (!code) {
|
|
222
|
+
logger.warn('Google OAuth callback missing code');
|
|
223
|
+
res.redirect(`${consoleUrl}/login?error=missing_code`);
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
let result;
|
|
227
|
+
try {
|
|
228
|
+
result = await exchangeCodeAndCreateUser(code, req);
|
|
229
|
+
}
|
|
230
|
+
catch (err) {
|
|
231
|
+
const errMsg = err instanceof Error ? err.message : String(err);
|
|
232
|
+
logger.error('Google OAuth exchange failed', { error: errMsg });
|
|
233
|
+
const errorCode = errMsg.split(':')[0] || 'exchange_failed';
|
|
234
|
+
res.redirect(`${consoleUrl}/login?error=${encodeURIComponent(errorCode)}`);
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
// Resolve device ID for JWT (enables Cloud-side auto-registration on poll/send)
|
|
238
|
+
let deviceId;
|
|
239
|
+
try {
|
|
240
|
+
const identity = await DeviceIdentityService.getInstance().getOrCreateIdentity();
|
|
241
|
+
deviceId = identity.deviceId;
|
|
242
|
+
}
|
|
243
|
+
catch {
|
|
244
|
+
logger.debug('Could not resolve deviceId for JWT (non-fatal)');
|
|
245
|
+
}
|
|
246
|
+
// Issue JWT access token
|
|
247
|
+
const now = Math.floor(Date.now() / 1000);
|
|
248
|
+
const accessToken = signJwt({
|
|
249
|
+
sub: result.user.id,
|
|
250
|
+
email: result.profile.email,
|
|
251
|
+
name: result.profile.name || '',
|
|
252
|
+
plan: DEFAULT_USER_PLAN,
|
|
253
|
+
...(deviceId && { deviceId }),
|
|
254
|
+
iat: now,
|
|
255
|
+
exp: now + AUTH_CONSTANTS.JWT.ACCESS_TOKEN_EXPIRY_S,
|
|
256
|
+
iss: AUTH_CONSTANTS.JWT.ISSUER,
|
|
257
|
+
type: 'access',
|
|
258
|
+
});
|
|
259
|
+
// Issue refresh token (long-lived, enables auto-renewal after access token expires)
|
|
260
|
+
const refreshToken = signJwt({
|
|
261
|
+
sub: result.user.id,
|
|
262
|
+
email: result.profile.email,
|
|
263
|
+
name: result.profile.name || '',
|
|
264
|
+
plan: DEFAULT_USER_PLAN,
|
|
265
|
+
...(deviceId && { deviceId }),
|
|
266
|
+
iat: now,
|
|
267
|
+
exp: now + AUTH_CONSTANTS.JWT.REFRESH_TOKEN_EXPIRY_S,
|
|
268
|
+
iss: AUTH_CONSTANTS.JWT.ISSUER,
|
|
269
|
+
type: 'refresh',
|
|
270
|
+
});
|
|
271
|
+
// Parse state for post-login redirect
|
|
272
|
+
let postLoginRedirect = '';
|
|
273
|
+
if (state) {
|
|
274
|
+
try {
|
|
275
|
+
const parsed = JSON.parse(Buffer.from(state, 'base64url').toString('utf8'));
|
|
276
|
+
postLoginRedirect = parsed.redirectTo || parsed.redirect || '';
|
|
277
|
+
}
|
|
278
|
+
catch {
|
|
279
|
+
logger.warn('Failed to parse OAuth state parameter');
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
const finalRedirect = postLoginRedirect || consoleUrl;
|
|
283
|
+
const separator = finalRedirect.includes('?') ? '&' : '?';
|
|
284
|
+
// Persist tokens to CloudClientService immediately so the refresh token
|
|
285
|
+
// is written to ~/.crewly/cloud/config.json before the frontend calls /connect.
|
|
286
|
+
try {
|
|
287
|
+
const cloudUrl = `${req.protocol}://${req.get('host')}`;
|
|
288
|
+
const client = CloudClientService.getInstance();
|
|
289
|
+
client.connectLocal(cloudUrl, accessToken, DEFAULT_USER_PLAN, refreshToken);
|
|
290
|
+
}
|
|
291
|
+
catch (connectErr) {
|
|
292
|
+
logger.warn('Failed to connect CloudClientService during OAuth callback (non-fatal)', {
|
|
293
|
+
error: connectErr instanceof Error ? connectErr.message : String(connectErr),
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
logger.info('Cloud Google OAuth login successful', { email: result.profile.email, userId: result.user.id });
|
|
297
|
+
res.redirect(`${finalRedirect}${separator}token=${accessToken}&refreshToken=${refreshToken}`);
|
|
298
|
+
}
|
|
299
|
+
catch (error) {
|
|
300
|
+
logger.error('Cloud Google OAuth callback error', {
|
|
301
|
+
error: error instanceof Error ? error.message : String(error),
|
|
302
|
+
});
|
|
303
|
+
next(error);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
/**
|
|
307
|
+
* POST /api/cloud/google/url
|
|
308
|
+
*
|
|
309
|
+
* Returns the Google OAuth consent URL as JSON (for SPA clients).
|
|
310
|
+
*
|
|
311
|
+
* @param req - Request with optional body: { redirectTo, redirectUrl }
|
|
312
|
+
* @param res - Response returning { success, data: { url } }
|
|
313
|
+
* @param next - Next function for error propagation
|
|
314
|
+
*/
|
|
315
|
+
export async function cloudGoogleUrl(req, res, next) {
|
|
316
|
+
try {
|
|
317
|
+
const postLoginRedirect = req.body?.redirectTo || req.body?.redirectUrl || '';
|
|
318
|
+
const url = buildGoogleOAuthUrl(req, postLoginRedirect);
|
|
319
|
+
logger.info('Returning Google OAuth URL for SPA client');
|
|
320
|
+
res.json({ success: true, data: { url } });
|
|
321
|
+
}
|
|
322
|
+
catch (error) {
|
|
323
|
+
if (error instanceof Error && error.message.includes('GOOGLE_CLIENT_ID')) {
|
|
324
|
+
res.status(500).json({ success: false, error: error.message });
|
|
325
|
+
return;
|
|
326
|
+
}
|
|
327
|
+
logger.error('Failed to generate Google OAuth URL', {
|
|
328
|
+
error: error instanceof Error ? error.message : String(error),
|
|
329
|
+
});
|
|
330
|
+
next(error);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
/**
|
|
334
|
+
* POST /api/cloud/google/callback
|
|
335
|
+
*
|
|
336
|
+
* SPA-friendly code exchange: accepts { code } in body, returns JWT + user as JSON.
|
|
337
|
+
*
|
|
338
|
+
* @param req - Request with body: { code }
|
|
339
|
+
* @param res - Response returning { success, data: { accessToken, refreshToken, expiresIn, user } }
|
|
340
|
+
* @param next - Next function for error propagation
|
|
341
|
+
*/
|
|
342
|
+
export async function cloudGoogleCallbackPost(req, res, next) {
|
|
343
|
+
try {
|
|
344
|
+
const { code } = req.body;
|
|
345
|
+
if (!code) {
|
|
346
|
+
res.status(400).json({ success: false, error: 'Missing authorization code' });
|
|
347
|
+
return;
|
|
348
|
+
}
|
|
349
|
+
const result = await exchangeCodeAndCreateUser(code, req);
|
|
350
|
+
// Resolve device ID for JWT (enables Cloud-side auto-registration on poll/send)
|
|
351
|
+
let deviceIdForJwt;
|
|
352
|
+
try {
|
|
353
|
+
const identity = await DeviceIdentityService.getInstance().getOrCreateIdentity();
|
|
354
|
+
deviceIdForJwt = identity.deviceId;
|
|
355
|
+
}
|
|
356
|
+
catch {
|
|
357
|
+
logger.debug('Could not resolve deviceId for JWT (non-fatal)');
|
|
358
|
+
}
|
|
359
|
+
// Issue access token
|
|
360
|
+
const now = Math.floor(Date.now() / 1000);
|
|
361
|
+
const accessToken = signJwt({
|
|
362
|
+
sub: result.user.id,
|
|
363
|
+
email: result.profile.email,
|
|
364
|
+
name: result.profile.name || '',
|
|
365
|
+
plan: DEFAULT_USER_PLAN,
|
|
366
|
+
...(deviceIdForJwt && { deviceId: deviceIdForJwt }),
|
|
367
|
+
iat: now,
|
|
368
|
+
exp: now + AUTH_CONSTANTS.JWT.ACCESS_TOKEN_EXPIRY_S,
|
|
369
|
+
iss: AUTH_CONSTANTS.JWT.ISSUER,
|
|
370
|
+
type: 'access',
|
|
371
|
+
});
|
|
372
|
+
// Issue refresh token (longer lived, carries user claims for token refresh)
|
|
373
|
+
const refreshToken = signJwt({
|
|
374
|
+
sub: result.user.id,
|
|
375
|
+
email: result.profile.email,
|
|
376
|
+
name: result.profile.name || '',
|
|
377
|
+
plan: DEFAULT_USER_PLAN,
|
|
378
|
+
...(deviceIdForJwt && { deviceId: deviceIdForJwt }),
|
|
379
|
+
iat: now,
|
|
380
|
+
exp: now + AUTH_CONSTANTS.JWT.REFRESH_TOKEN_EXPIRY_S,
|
|
381
|
+
iss: AUTH_CONSTANTS.JWT.ISSUER,
|
|
382
|
+
type: 'refresh',
|
|
383
|
+
});
|
|
384
|
+
// Persist tokens to CloudClientService immediately so the refresh token
|
|
385
|
+
// is written to ~/.crewly/cloud/config.json before the frontend calls /connect.
|
|
386
|
+
try {
|
|
387
|
+
const cloudUrl = `${req.protocol}://${req.get('host')}`;
|
|
388
|
+
const client = CloudClientService.getInstance();
|
|
389
|
+
client.connectLocal(cloudUrl, accessToken, DEFAULT_USER_PLAN, refreshToken);
|
|
390
|
+
}
|
|
391
|
+
catch (connectErr) {
|
|
392
|
+
logger.warn('Failed to connect CloudClientService during OAuth POST callback (non-fatal)', {
|
|
393
|
+
error: connectErr instanceof Error ? connectErr.message : String(connectErr),
|
|
394
|
+
});
|
|
395
|
+
}
|
|
396
|
+
logger.info('Cloud Google OAuth login successful (POST)', { email: result.profile.email, userId: result.user.id });
|
|
397
|
+
res.json({
|
|
398
|
+
success: true,
|
|
399
|
+
data: {
|
|
400
|
+
accessToken,
|
|
401
|
+
refreshToken,
|
|
402
|
+
expiresIn: AUTH_CONSTANTS.JWT.ACCESS_TOKEN_EXPIRY_S,
|
|
403
|
+
user: {
|
|
404
|
+
id: result.user.id,
|
|
405
|
+
email: result.profile.email,
|
|
406
|
+
displayName: result.profile.name || '',
|
|
407
|
+
plan: DEFAULT_USER_PLAN,
|
|
408
|
+
createdAt: result.user.createdAt || '',
|
|
409
|
+
},
|
|
410
|
+
},
|
|
411
|
+
});
|
|
412
|
+
}
|
|
413
|
+
catch (error) {
|
|
414
|
+
const errMsg = error instanceof Error ? error.message : String(error);
|
|
415
|
+
if (errMsg.includes('credentials not configured')) {
|
|
416
|
+
res.status(500).json({ success: false, error: 'Google OAuth credentials not configured' });
|
|
417
|
+
return;
|
|
418
|
+
}
|
|
419
|
+
if (errMsg.startsWith('token_exchange_failed') || errMsg.startsWith('profile_fetch_failed') || errMsg === 'no_email') {
|
|
420
|
+
res.status(400).json({ success: false, error: errMsg.includes(':') ? errMsg.split(':')[0] : errMsg });
|
|
421
|
+
return;
|
|
422
|
+
}
|
|
423
|
+
logger.error('Cloud Google OAuth callback (POST) error', { error: errMsg });
|
|
424
|
+
next(error);
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
//# sourceMappingURL=cloud-google-auth.controller.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cloud-google-auth.controller.js","sourceRoot":"","sources":["../../../../../../backend/src/controllers/cloud/cloud-google-auth.controller.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Backup Archive Service (P0)
|
|
3
|
+
*
|
|
4
|
+
* Builds a portable workspace backup: a single `.tar.gz` containing a
|
|
5
|
+
* top-level `manifest.json`, the captured `CREWLY_HOME` globals (under
|
|
6
|
+
* `home/`), each project's `.crewly/` tree (under `projects/<id>/`), and
|
|
7
|
+
* optionally `chat.db`.
|
|
8
|
+
*
|
|
9
|
+
* Runs locally with no Cloud dependency — `crewly backup create`. The Cloud
|
|
10
|
+
* upload/park step (Pro-gated) is a later phase and consumes the archive this
|
|
11
|
+
* service produces. See specs/2026-06-07-workspace-backup.md.
|
|
12
|
+
*
|
|
13
|
+
* @module services/backup/backup-archive.service
|
|
14
|
+
*/
|
|
15
|
+
import { type ComponentLogger } from '../core/logger.service.js';
|
|
16
|
+
import { type CreateBackupOptions, type CreateBackupResult } from './backup.types.js';
|
|
17
|
+
/**
|
|
18
|
+
* Service that builds workspace backup archives.
|
|
19
|
+
*/
|
|
20
|
+
export declare class BackupArchiveService {
|
|
21
|
+
private readonly logger;
|
|
22
|
+
constructor(logger?: ComponentLogger);
|
|
23
|
+
/**
|
|
24
|
+
* Build a workspace backup archive.
|
|
25
|
+
*
|
|
26
|
+
* Captures CREWLY_HOME globals (exclude-based, so new data domains are
|
|
27
|
+
* picked up automatically), each project's `.crewly/` tree (walked from
|
|
28
|
+
* projects.json) with git provenance, and chat.db (unless excluded). Stages
|
|
29
|
+
* everything into a temp dir, writes the manifest, and tars it to `outPath`.
|
|
30
|
+
*
|
|
31
|
+
* @param options - Build options (createdAt is required — no clock in lib code)
|
|
32
|
+
* @returns The archive path, manifest, and total captured bytes
|
|
33
|
+
* @throws If CREWLY_HOME does not exist or the tar write fails
|
|
34
|
+
*/
|
|
35
|
+
createArchive(options: CreateBackupOptions): Promise<CreateBackupResult>;
|
|
36
|
+
/**
|
|
37
|
+
* Recursively collect capturable global files under CREWLY_HOME (relative
|
|
38
|
+
* paths). Exclude-based so new data domains are captured automatically.
|
|
39
|
+
*
|
|
40
|
+
* @param home - CREWLY_HOME absolute path
|
|
41
|
+
* @returns Relative file paths (POSIX-style) to capture
|
|
42
|
+
*/
|
|
43
|
+
private collectGlobalFiles;
|
|
44
|
+
/**
|
|
45
|
+
* Walk projects.json and capture each project's `.crewly/` tree + git
|
|
46
|
+
* provenance into staging/projects/<id>/.
|
|
47
|
+
*
|
|
48
|
+
* @param home - CREWLY_HOME absolute path
|
|
49
|
+
* @param staging - staging dir root
|
|
50
|
+
* @returns Project entries for the manifest
|
|
51
|
+
*/
|
|
52
|
+
private collectProjects;
|
|
53
|
+
/**
|
|
54
|
+
* Read `origin` remote + HEAD commit for a project dir (best-effort).
|
|
55
|
+
*
|
|
56
|
+
* @param projectPath - Absolute project path
|
|
57
|
+
* @returns Git provenance (nulls when not a repo / unavailable)
|
|
58
|
+
*/
|
|
59
|
+
private readGitProvenance;
|
|
60
|
+
/**
|
|
61
|
+
* Capture chat.db via the SQLite online backup API (consistent snapshot of a
|
|
62
|
+
* possibly-live DB). Degrades gracefully when chat.db is absent or the native
|
|
63
|
+
* module can't load — the backup just omits it with a recorded reason.
|
|
64
|
+
*
|
|
65
|
+
* @param home - CREWLY_HOME absolute path
|
|
66
|
+
* @param staging - staging dir root
|
|
67
|
+
* @returns chat.db manifest record
|
|
68
|
+
*/
|
|
69
|
+
private captureChatDb;
|
|
70
|
+
/**
|
|
71
|
+
* Copy a source file into the staging archive layout, returning its manifest
|
|
72
|
+
* entry (archive-relative path + sha256 + size).
|
|
73
|
+
*
|
|
74
|
+
* @param srcRoot - Root the relative path is resolved against
|
|
75
|
+
* @param rel - Source path relative to srcRoot (POSIX-style)
|
|
76
|
+
* @param staging - staging dir root
|
|
77
|
+
* @param archivePath - Destination path inside the archive
|
|
78
|
+
* @returns Manifest file entry
|
|
79
|
+
*/
|
|
80
|
+
private stageFile;
|
|
81
|
+
/**
|
|
82
|
+
* Read the running Crewly version (best-effort). Uses npm_package_version —
|
|
83
|
+
* the same source as tracing.service.ts — which works under both the ESM
|
|
84
|
+
* runtime and the CJS test runner (no import.meta / __dirname).
|
|
85
|
+
*
|
|
86
|
+
* @returns Version string or 'unknown'
|
|
87
|
+
*/
|
|
88
|
+
private readCrewlyVersion;
|
|
89
|
+
}
|
|
90
|
+
//# sourceMappingURL=backup-archive.service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"backup-archive.service.d.ts","sourceRoot":"","sources":["../../../../../../backend/src/services/backup/backup-archive.service.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAYH,OAAO,EAAiB,KAAK,eAAe,EAAE,MAAM,2BAA2B,CAAC;AAChF,OAAO,EAKL,KAAK,mBAAmB,EACxB,KAAK,kBAAkB,EACxB,MAAM,mBAAmB,CAAC;AAkC3B;;GAEG;AACH,qBAAa,oBAAoB;IAC/B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAkB;gBAE7B,MAAM,CAAC,EAAE,eAAe;IAIpC;;;;;;;;;;;OAWG;IACG,aAAa,CAAC,OAAO,EAAE,mBAAmB,GAAG,OAAO,CAAC,kBAAkB,CAAC;IAmE9E;;;;;;OAMG;YACW,kBAAkB;IAmBhC;;;;;;;OAOG;YACW,eAAe;IAqC7B;;;;;OAKG;YACW,iBAAiB;IAgB/B;;;;;;;;OAQG;YACW,aAAa;IA6B3B;;;;;;;;;OASG;YACW,SAAS;IASvB;;;;;;OAMG;IACH,OAAO,CAAC,iBAAiB;CAG1B"}
|