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 @@
|
|
|
1
|
+
{"version":3,"file":"device-identity.service.d.ts","sourceRoot":"","sources":["../../../../../../backend/src/services/cloud/device-identity.service.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AASH;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,yCAAyC;IACzC,QAAQ,EAAE,MAAM,CAAC;IACjB,2DAA2D;IAC3D,UAAU,EAAE,MAAM,CAAC;IACnB,wDAAwD;IACxD,SAAS,EAAE,MAAM,CAAC;IAClB,qCAAqC;IACrC,UAAU,EAAE,MAAM,CAAC;CACpB;AAKD;;;;;;;;;;;;GAYG;AACH,qBAAa,qBAAqB;IAChC,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAsC;IAC7D,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAkB;IACzC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAS;IACxC,OAAO,CAAC,cAAc,CAA+B;IAErD;;;;OAIG;gBACS,UAAU,CAAC,EAAE,MAAM;IAM/B;;;;OAIG;IACH,MAAM,CAAC,WAAW,IAAI,qBAAqB;IAO3C;;OAEG;IACH,MAAM,CAAC,aAAa,IAAI,IAAI;IAI5B;;;;;;OAMG;IACG,mBAAmB,IAAI,OAAO,CAAC,cAAc,CAAC;IAiCpD;;OAEG;IACG,cAAc,IAAI,OAAO,CAAC,IAAI,CAAC;IAOrC;;;;OAIG;IACG,WAAW,IAAI,OAAO,CAAC,MAAM,CAAC;IAKpC;;;;OAIG;IACG,aAAa,IAAI,OAAO,CAAC,MAAM,CAAC;IAKtC;;;;OAIG;YACW,eAAe;CAa9B"}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Device Identity Service
|
|
3
|
+
*
|
|
4
|
+
* Manages a persistent device identity stored in ~/.crewly/device.json.
|
|
5
|
+
* Used by the relay auto-discovery system to uniquely identify this
|
|
6
|
+
* Crewly installation across sessions and restarts.
|
|
7
|
+
*
|
|
8
|
+
* @module services/cloud/device-identity.service
|
|
9
|
+
*/
|
|
10
|
+
import { readFile, writeFile, mkdir } from 'fs/promises';
|
|
11
|
+
import { existsSync } from 'fs';
|
|
12
|
+
import { join } from 'path';
|
|
13
|
+
import { homedir, hostname } from 'os';
|
|
14
|
+
import { v4 as uuidv4 } from 'uuid';
|
|
15
|
+
import { LoggerService } from '../core/logger.service.js';
|
|
16
|
+
/** Path to device identity file relative to ~/.crewly/ */
|
|
17
|
+
const DEVICE_FILE = 'device.json';
|
|
18
|
+
/**
|
|
19
|
+
* Singleton service that manages device identity for relay auto-discovery.
|
|
20
|
+
*
|
|
21
|
+
* On first run, generates a UUID and captures the OS hostname, persisting
|
|
22
|
+
* both to ~/.crewly/device.json. Subsequent calls read from disk.
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* ```typescript
|
|
26
|
+
* const identity = DeviceIdentityService.getInstance();
|
|
27
|
+
* const id = await identity.getOrCreateIdentity();
|
|
28
|
+
* console.log(id.deviceId, id.deviceName);
|
|
29
|
+
* ```
|
|
30
|
+
*/
|
|
31
|
+
export class DeviceIdentityService {
|
|
32
|
+
static instance = null;
|
|
33
|
+
logger;
|
|
34
|
+
crewlyHome;
|
|
35
|
+
deviceFilePath;
|
|
36
|
+
cachedIdentity = null;
|
|
37
|
+
/**
|
|
38
|
+
* Creates a DeviceIdentityService instance.
|
|
39
|
+
*
|
|
40
|
+
* @param crewlyHome - Override ~/.crewly path (for testing)
|
|
41
|
+
*/
|
|
42
|
+
constructor(crewlyHome) {
|
|
43
|
+
this.logger = LoggerService.getInstance().createComponentLogger('DeviceIdentityService');
|
|
44
|
+
this.crewlyHome = crewlyHome || join(homedir(), '.crewly');
|
|
45
|
+
this.deviceFilePath = join(this.crewlyHome, DEVICE_FILE);
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Get the singleton instance.
|
|
49
|
+
*
|
|
50
|
+
* @returns DeviceIdentityService instance
|
|
51
|
+
*/
|
|
52
|
+
static getInstance() {
|
|
53
|
+
if (!DeviceIdentityService.instance) {
|
|
54
|
+
DeviceIdentityService.instance = new DeviceIdentityService();
|
|
55
|
+
}
|
|
56
|
+
return DeviceIdentityService.instance;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Reset the singleton (for testing).
|
|
60
|
+
*/
|
|
61
|
+
static resetInstance() {
|
|
62
|
+
DeviceIdentityService.instance = null;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Get or create the device identity.
|
|
66
|
+
* Reads from ~/.crewly/device.json if it exists, otherwise creates
|
|
67
|
+
* a new identity with a fresh UUID and the OS hostname.
|
|
68
|
+
*
|
|
69
|
+
* @returns The device identity
|
|
70
|
+
*/
|
|
71
|
+
async getOrCreateIdentity() {
|
|
72
|
+
if (this.cachedIdentity) {
|
|
73
|
+
return this.cachedIdentity;
|
|
74
|
+
}
|
|
75
|
+
try {
|
|
76
|
+
if (existsSync(this.deviceFilePath)) {
|
|
77
|
+
const content = await readFile(this.deviceFilePath, 'utf-8');
|
|
78
|
+
const identity = JSON.parse(content);
|
|
79
|
+
this.cachedIdentity = identity;
|
|
80
|
+
this.logger.debug('Loaded device identity from disk', { deviceId: identity.deviceId });
|
|
81
|
+
return identity;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
catch (error) {
|
|
85
|
+
this.logger.warn('Failed to read device identity, creating new one', {
|
|
86
|
+
error: error instanceof Error ? error.message : String(error),
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
// Create new identity
|
|
90
|
+
const identity = {
|
|
91
|
+
deviceId: uuidv4(),
|
|
92
|
+
deviceName: hostname(),
|
|
93
|
+
createdAt: new Date().toISOString(),
|
|
94
|
+
lastSeenAt: new Date().toISOString(),
|
|
95
|
+
};
|
|
96
|
+
await this.persistIdentity(identity);
|
|
97
|
+
this.cachedIdentity = identity;
|
|
98
|
+
this.logger.info('Created new device identity', { deviceId: identity.deviceId, deviceName: identity.deviceName });
|
|
99
|
+
return identity;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Update the lastSeenAt timestamp on the persisted identity.
|
|
103
|
+
*/
|
|
104
|
+
async updateLastSeen() {
|
|
105
|
+
const identity = await this.getOrCreateIdentity();
|
|
106
|
+
identity.lastSeenAt = new Date().toISOString();
|
|
107
|
+
await this.persistIdentity(identity);
|
|
108
|
+
this.logger.debug('Updated lastSeenAt', { deviceId: identity.deviceId });
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Get the device ID string.
|
|
112
|
+
*
|
|
113
|
+
* @returns The device UUID
|
|
114
|
+
*/
|
|
115
|
+
async getDeviceId() {
|
|
116
|
+
const identity = await this.getOrCreateIdentity();
|
|
117
|
+
return identity.deviceId;
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Get the device name string.
|
|
121
|
+
*
|
|
122
|
+
* @returns The device hostname
|
|
123
|
+
*/
|
|
124
|
+
async getDeviceName() {
|
|
125
|
+
const identity = await this.getOrCreateIdentity();
|
|
126
|
+
return identity.deviceName;
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Write identity to disk, ensuring the directory exists.
|
|
130
|
+
*
|
|
131
|
+
* @param identity - The identity to persist
|
|
132
|
+
*/
|
|
133
|
+
async persistIdentity(identity) {
|
|
134
|
+
try {
|
|
135
|
+
if (!existsSync(this.crewlyHome)) {
|
|
136
|
+
await mkdir(this.crewlyHome, { recursive: true });
|
|
137
|
+
}
|
|
138
|
+
await writeFile(this.deviceFilePath, JSON.stringify(identity, null, 2), 'utf-8');
|
|
139
|
+
}
|
|
140
|
+
catch (error) {
|
|
141
|
+
this.logger.error('Failed to persist device identity', {
|
|
142
|
+
error: error instanceof Error ? error.message : String(error),
|
|
143
|
+
});
|
|
144
|
+
throw error;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
//# sourceMappingURL=device-identity.service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"device-identity.service.js","sourceRoot":"","sources":["../../../../../../backend/src/services/cloud/device-identity.service.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
export type ConnectedProvider = 'google' | 'github' | 'calendar' | 'drive';
|
|
2
|
+
export interface ConnectedService {
|
|
3
|
+
provider: ConnectedProvider;
|
|
4
|
+
encryptedRefreshToken: string;
|
|
5
|
+
encryptedAccessToken?: string;
|
|
6
|
+
scopes: string[];
|
|
7
|
+
connectedAt: string;
|
|
8
|
+
}
|
|
9
|
+
export interface UserIdentity {
|
|
10
|
+
id: string;
|
|
11
|
+
email: string;
|
|
12
|
+
slackUserId?: string;
|
|
13
|
+
connectedServices: ConnectedService[];
|
|
14
|
+
createdAt: string;
|
|
15
|
+
updatedAt: string;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Service for managing user identities and encrypted service tokens.
|
|
19
|
+
*
|
|
20
|
+
* Uses a file-backed JSON store with an in-memory cache for reads.
|
|
21
|
+
* Tokens are encrypted with AES-256-GCM using a key derived from
|
|
22
|
+
* environment variables.
|
|
23
|
+
*/
|
|
24
|
+
export declare class UserIdentityService {
|
|
25
|
+
private static instance;
|
|
26
|
+
private readonly logger;
|
|
27
|
+
private readonly storePath;
|
|
28
|
+
/** Cached encryption key (derived once from env) */
|
|
29
|
+
private cachedKey;
|
|
30
|
+
/** In-memory cache of the user store to avoid repeated disk reads */
|
|
31
|
+
private storeCache;
|
|
32
|
+
private constructor();
|
|
33
|
+
static getInstance(): UserIdentityService;
|
|
34
|
+
/**
|
|
35
|
+
* Reset the singleton instance (for testing).
|
|
36
|
+
*/
|
|
37
|
+
static resetInstance(): void;
|
|
38
|
+
private ensureStoreDir;
|
|
39
|
+
/**
|
|
40
|
+
* Load the user store from disk, using in-memory cache when available.
|
|
41
|
+
*
|
|
42
|
+
* @returns The parsed user store
|
|
43
|
+
*/
|
|
44
|
+
private loadStore;
|
|
45
|
+
/**
|
|
46
|
+
* Persist the user store to disk using atomic write (temp + rename).
|
|
47
|
+
*
|
|
48
|
+
* @param store - The store data to persist
|
|
49
|
+
*/
|
|
50
|
+
private saveStore;
|
|
51
|
+
/**
|
|
52
|
+
* Derive the AES-256 encryption key from environment variables.
|
|
53
|
+
* Caches the result to avoid recomputing SHA-256 on every call.
|
|
54
|
+
*
|
|
55
|
+
* @returns 32-byte key buffer
|
|
56
|
+
*/
|
|
57
|
+
private getKey;
|
|
58
|
+
/**
|
|
59
|
+
* Encrypt a plaintext token using AES-256-GCM.
|
|
60
|
+
*
|
|
61
|
+
* @param value - The plaintext token
|
|
62
|
+
* @returns Encrypted string in the format `iv.tag.ciphertext` (base64)
|
|
63
|
+
*/
|
|
64
|
+
encryptToken(value: string): string;
|
|
65
|
+
/**
|
|
66
|
+
* Decrypt an encrypted token string produced by {@link encryptToken}.
|
|
67
|
+
*
|
|
68
|
+
* @param value - Encrypted string in `iv.tag.ciphertext` format
|
|
69
|
+
* @returns The decrypted plaintext
|
|
70
|
+
* @throws Error if the token format is invalid or decryption fails
|
|
71
|
+
*/
|
|
72
|
+
decryptToken(value: string): string;
|
|
73
|
+
listUsers(): Promise<UserIdentity[]>;
|
|
74
|
+
getUserById(id: string): Promise<UserIdentity | null>;
|
|
75
|
+
getUserBySlackUserId(slackUserId: string): Promise<UserIdentity | null>;
|
|
76
|
+
createOrUpdateUser(input: {
|
|
77
|
+
email: string;
|
|
78
|
+
slackUserId?: string;
|
|
79
|
+
}): Promise<UserIdentity>;
|
|
80
|
+
connectService(userId: string, provider: ConnectedProvider, tokens: {
|
|
81
|
+
refreshToken: string;
|
|
82
|
+
accessToken?: string;
|
|
83
|
+
scopes: string[];
|
|
84
|
+
}): Promise<UserIdentity>;
|
|
85
|
+
}
|
|
86
|
+
//# sourceMappingURL=user-identity.service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"user-identity.service.d.ts","sourceRoot":"","sources":["../../../../../../backend/src/services/user/user-identity.service.ts"],"names":[],"mappings":"AAOA,MAAM,MAAM,iBAAiB,GAAG,QAAQ,GAAG,QAAQ,GAAG,UAAU,GAAG,OAAO,CAAC;AAE3E,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,iBAAiB,CAAC;IAC5B,qBAAqB,EAAE,MAAM,CAAC;IAC9B,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,iBAAiB,EAAE,gBAAgB,EAAE,CAAC;IACtC,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;CACnB;AASD;;;;;;GAMG;AACH,qBAAa,mBAAmB;IAC9B,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAoC;IAC3D,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAkB;IACzC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,oDAAoD;IACpD,OAAO,CAAC,SAAS,CAAuB;IACxC,qEAAqE;IACrE,OAAO,CAAC,UAAU,CAA0B;IAE5C,OAAO;IAKP,MAAM,CAAC,WAAW,IAAI,mBAAmB;IAOzC;;OAEG;IACH,MAAM,CAAC,aAAa,IAAI,IAAI;YAId,cAAc;IAI5B;;;;OAIG;YACW,SAAS;IAmBvB;;;;OAIG;YACW,SAAS;IAQvB;;;;;OAKG;IACH,OAAO,CAAC,MAAM;IAYd;;;;;OAKG;IACH,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM;IASnC;;;;;;OAMG;IACH,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM;IAgB7B,SAAS,IAAI,OAAO,CAAC,YAAY,EAAE,CAAC;IAKpC,WAAW,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC;IAKrD,oBAAoB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC;IAKvE,kBAAkB,CAAC,KAAK,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,WAAW,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,YAAY,CAAC;IA8BzF,cAAc,CAClB,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,iBAAiB,EAC3B,MAAM,EAAE;QAAE,YAAY,EAAE,MAAM,CAAC;QAAC,WAAW,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,EAAE,CAAA;KAAE,GACvE,OAAO,CAAC,YAAY,CAAC;CAwBzB"}
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import { promises as fs } from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import os from 'os';
|
|
4
|
+
import crypto from 'crypto';
|
|
5
|
+
import { CREWLY_CONSTANTS } from '../../constants.js';
|
|
6
|
+
import { LoggerService } from '../core/logger.service.js';
|
|
7
|
+
const STORE_SCHEMA_VERSION = 1;
|
|
8
|
+
/**
|
|
9
|
+
* Service for managing user identities and encrypted service tokens.
|
|
10
|
+
*
|
|
11
|
+
* Uses a file-backed JSON store with an in-memory cache for reads.
|
|
12
|
+
* Tokens are encrypted with AES-256-GCM using a key derived from
|
|
13
|
+
* environment variables.
|
|
14
|
+
*/
|
|
15
|
+
export class UserIdentityService {
|
|
16
|
+
static instance = null;
|
|
17
|
+
logger;
|
|
18
|
+
storePath;
|
|
19
|
+
/** Cached encryption key (derived once from env) */
|
|
20
|
+
cachedKey = null;
|
|
21
|
+
/** In-memory cache of the user store to avoid repeated disk reads */
|
|
22
|
+
storeCache = null;
|
|
23
|
+
constructor() {
|
|
24
|
+
this.logger = LoggerService.getInstance().createComponentLogger('UserIdentityService');
|
|
25
|
+
this.storePath = path.join(os.homedir(), CREWLY_CONSTANTS.PATHS.CREWLY_HOME, 'users.json');
|
|
26
|
+
}
|
|
27
|
+
static getInstance() {
|
|
28
|
+
if (!UserIdentityService.instance) {
|
|
29
|
+
UserIdentityService.instance = new UserIdentityService();
|
|
30
|
+
}
|
|
31
|
+
return UserIdentityService.instance;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Reset the singleton instance (for testing).
|
|
35
|
+
*/
|
|
36
|
+
static resetInstance() {
|
|
37
|
+
UserIdentityService.instance = null;
|
|
38
|
+
}
|
|
39
|
+
async ensureStoreDir() {
|
|
40
|
+
await fs.mkdir(path.dirname(this.storePath), { recursive: true });
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Load the user store from disk, using in-memory cache when available.
|
|
44
|
+
*
|
|
45
|
+
* @returns The parsed user store
|
|
46
|
+
*/
|
|
47
|
+
async loadStore() {
|
|
48
|
+
if (this.storeCache) {
|
|
49
|
+
return this.storeCache;
|
|
50
|
+
}
|
|
51
|
+
try {
|
|
52
|
+
const raw = await fs.readFile(this.storePath, 'utf8');
|
|
53
|
+
const parsed = JSON.parse(raw);
|
|
54
|
+
if (!Array.isArray(parsed.users)) {
|
|
55
|
+
this.storeCache = { schemaVersion: STORE_SCHEMA_VERSION, users: [] };
|
|
56
|
+
return this.storeCache;
|
|
57
|
+
}
|
|
58
|
+
this.storeCache = parsed;
|
|
59
|
+
return this.storeCache;
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
this.storeCache = { schemaVersion: STORE_SCHEMA_VERSION, users: [] };
|
|
63
|
+
return this.storeCache;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Persist the user store to disk using atomic write (temp + rename).
|
|
68
|
+
*
|
|
69
|
+
* @param store - The store data to persist
|
|
70
|
+
*/
|
|
71
|
+
async saveStore(store) {
|
|
72
|
+
await this.ensureStoreDir();
|
|
73
|
+
const tmpPath = `${this.storePath}.tmp`;
|
|
74
|
+
await fs.writeFile(tmpPath, JSON.stringify(store, null, 2) + '\n', 'utf8');
|
|
75
|
+
await fs.rename(tmpPath, this.storePath);
|
|
76
|
+
this.storeCache = store;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Derive the AES-256 encryption key from environment variables.
|
|
80
|
+
* Caches the result to avoid recomputing SHA-256 on every call.
|
|
81
|
+
*
|
|
82
|
+
* @returns 32-byte key buffer
|
|
83
|
+
*/
|
|
84
|
+
getKey() {
|
|
85
|
+
if (this.cachedKey) {
|
|
86
|
+
return this.cachedKey;
|
|
87
|
+
}
|
|
88
|
+
const source = process.env.CREWLY_TOKEN_ENCRYPTION_KEY || process.env.CREWLY_SECRET;
|
|
89
|
+
if (!source) {
|
|
90
|
+
this.logger.warn('No CREWLY_TOKEN_ENCRYPTION_KEY or CREWLY_SECRET set — using insecure fallback key');
|
|
91
|
+
}
|
|
92
|
+
this.cachedKey = crypto.createHash('sha256').update(source || 'crewly-local-dev-key').digest();
|
|
93
|
+
return this.cachedKey;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Encrypt a plaintext token using AES-256-GCM.
|
|
97
|
+
*
|
|
98
|
+
* @param value - The plaintext token
|
|
99
|
+
* @returns Encrypted string in the format `iv.tag.ciphertext` (base64)
|
|
100
|
+
*/
|
|
101
|
+
encryptToken(value) {
|
|
102
|
+
const iv = crypto.randomBytes(12);
|
|
103
|
+
const key = this.getKey();
|
|
104
|
+
const cipher = crypto.createCipheriv('aes-256-gcm', key, iv);
|
|
105
|
+
const encrypted = Buffer.concat([cipher.update(value, 'utf8'), cipher.final()]);
|
|
106
|
+
const tag = cipher.getAuthTag();
|
|
107
|
+
return `${iv.toString('base64')}.${tag.toString('base64')}.${encrypted.toString('base64')}`;
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Decrypt an encrypted token string produced by {@link encryptToken}.
|
|
111
|
+
*
|
|
112
|
+
* @param value - Encrypted string in `iv.tag.ciphertext` format
|
|
113
|
+
* @returns The decrypted plaintext
|
|
114
|
+
* @throws Error if the token format is invalid or decryption fails
|
|
115
|
+
*/
|
|
116
|
+
decryptToken(value) {
|
|
117
|
+
const parts = value.split('.');
|
|
118
|
+
if (parts.length !== 3 || parts.some((p) => !p)) {
|
|
119
|
+
throw new Error('Invalid encrypted token format: expected iv.tag.ciphertext');
|
|
120
|
+
}
|
|
121
|
+
const [ivB64, tagB64, bodyB64] = parts;
|
|
122
|
+
const key = this.getKey();
|
|
123
|
+
const decipher = crypto.createDecipheriv('aes-256-gcm', key, Buffer.from(ivB64, 'base64'));
|
|
124
|
+
decipher.setAuthTag(Buffer.from(tagB64, 'base64'));
|
|
125
|
+
const decrypted = Buffer.concat([
|
|
126
|
+
decipher.update(Buffer.from(bodyB64, 'base64')),
|
|
127
|
+
decipher.final(),
|
|
128
|
+
]);
|
|
129
|
+
return decrypted.toString('utf8');
|
|
130
|
+
}
|
|
131
|
+
async listUsers() {
|
|
132
|
+
const store = await this.loadStore();
|
|
133
|
+
return store.users;
|
|
134
|
+
}
|
|
135
|
+
async getUserById(id) {
|
|
136
|
+
const store = await this.loadStore();
|
|
137
|
+
return store.users.find((u) => u.id === id) || null;
|
|
138
|
+
}
|
|
139
|
+
async getUserBySlackUserId(slackUserId) {
|
|
140
|
+
const store = await this.loadStore();
|
|
141
|
+
return store.users.find((u) => u.slackUserId === slackUserId) || null;
|
|
142
|
+
}
|
|
143
|
+
async createOrUpdateUser(input) {
|
|
144
|
+
const store = await this.loadStore();
|
|
145
|
+
const now = new Date().toISOString();
|
|
146
|
+
const existing = store.users.find((u) => u.email.toLowerCase() === input.email.toLowerCase() || (input.slackUserId && u.slackUserId === input.slackUserId));
|
|
147
|
+
if (existing) {
|
|
148
|
+
existing.email = input.email;
|
|
149
|
+
if (input.slackUserId) {
|
|
150
|
+
existing.slackUserId = input.slackUserId;
|
|
151
|
+
}
|
|
152
|
+
existing.updatedAt = now;
|
|
153
|
+
await this.saveStore(store);
|
|
154
|
+
return existing;
|
|
155
|
+
}
|
|
156
|
+
const created = {
|
|
157
|
+
id: crypto.randomUUID(),
|
|
158
|
+
email: input.email,
|
|
159
|
+
slackUserId: input.slackUserId,
|
|
160
|
+
connectedServices: [],
|
|
161
|
+
createdAt: now,
|
|
162
|
+
updatedAt: now,
|
|
163
|
+
};
|
|
164
|
+
store.users.push(created);
|
|
165
|
+
await this.saveStore(store);
|
|
166
|
+
return created;
|
|
167
|
+
}
|
|
168
|
+
async connectService(userId, provider, tokens) {
|
|
169
|
+
const store = await this.loadStore();
|
|
170
|
+
const user = store.users.find((u) => u.id === userId);
|
|
171
|
+
if (!user) {
|
|
172
|
+
throw new Error(`User not found: ${userId}`);
|
|
173
|
+
}
|
|
174
|
+
const now = new Date().toISOString();
|
|
175
|
+
const next = {
|
|
176
|
+
provider,
|
|
177
|
+
encryptedRefreshToken: this.encryptToken(tokens.refreshToken),
|
|
178
|
+
encryptedAccessToken: tokens.accessToken ? this.encryptToken(tokens.accessToken) : undefined,
|
|
179
|
+
scopes: tokens.scopes,
|
|
180
|
+
connectedAt: now,
|
|
181
|
+
};
|
|
182
|
+
user.connectedServices = user.connectedServices.filter((s) => s.provider !== provider);
|
|
183
|
+
user.connectedServices.push(next);
|
|
184
|
+
user.updatedAt = now;
|
|
185
|
+
await this.saveStore(store);
|
|
186
|
+
this.logger.info('Connected service for user', { userId, provider, scopeCount: tokens.scopes.length });
|
|
187
|
+
return user;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
//# sourceMappingURL=user-identity.service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"user-identity.service.js","sourceRoot":"","sources":["../../../../../../backend/src/services/user/user-identity.service.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `crewly backup` — workspace backup CLI (P0: local `create`).
|
|
3
|
+
*
|
|
4
|
+
* Builds a portable `.tar.gz` of this machine's workspace (CREWLY_HOME globals
|
|
5
|
+
* + each project's `.crewly/` + chat.db) that can later be restored on another
|
|
6
|
+
* machine. Runs fully locally and offline. Cloud push/pull/list (Pro-gated)
|
|
7
|
+
* land in later phases. See specs/2026-06-07-workspace-backup.md.
|
|
8
|
+
*
|
|
9
|
+
* @module cli/commands/backup
|
|
10
|
+
*/
|
|
11
|
+
/** Options accepted by `crewly backup`. */
|
|
12
|
+
export interface BackupCommandOptions {
|
|
13
|
+
/** Output archive path (create). */
|
|
14
|
+
out?: string;
|
|
15
|
+
/** Exclude chat.db from the archive (create). commander sets chatDb=false for --no-chat-db. */
|
|
16
|
+
chatDb?: boolean;
|
|
17
|
+
/** Restore conflict mode: 'abort' (default) | 'overwrite'. */
|
|
18
|
+
mode?: string;
|
|
19
|
+
/** Restore source→target path remaps, each "OLD=NEW" (repeatable). */
|
|
20
|
+
map?: string[];
|
|
21
|
+
/** Actually apply the restore. Without this, restore is a dry-run preview. */
|
|
22
|
+
apply?: boolean;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* `crewly backup <action>` dispatcher.
|
|
26
|
+
*
|
|
27
|
+
* @param action - Subcommand: `create` (P0). Others are placeholders.
|
|
28
|
+
* @param options - CLI options
|
|
29
|
+
*/
|
|
30
|
+
export declare function backupCommand(action: string, target?: string, options?: BackupCommandOptions): Promise<void>;
|
|
31
|
+
//# sourceMappingURL=backup.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"backup.d.ts","sourceRoot":"","sources":["../../../../../cli/src/commands/backup.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAiBH,2CAA2C;AAC3C,MAAM,WAAW,oBAAoB;IACnC,oCAAoC;IACpC,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,+FAA+F;IAC/F,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,8DAA8D;IAC9D,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,sEAAsE;IACtE,GAAG,CAAC,EAAE,MAAM,EAAE,CAAC;IACf,8EAA8E;IAC9E,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AA+BD;;;;;GAKG;AACH,wBAAsB,aAAa,CACjC,MAAM,EAAE,MAAM,EACd,MAAM,CAAC,EAAE,MAAM,EACf,OAAO,GAAE,oBAAyB,GACjC,OAAO,CAAC,IAAI,CAAC,CAuBf"}
|