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.
Files changed (75) hide show
  1. package/dist/backend/backend/src/constants.d.ts +22 -1
  2. package/dist/backend/backend/src/constants.d.ts.map +1 -1
  3. package/dist/backend/backend/src/constants.js +22 -1
  4. package/dist/backend/backend/src/constants.js.map +1 -1
  5. package/dist/backend/backend/src/services/backup/backup-archive.service.d.ts +90 -0
  6. package/dist/backend/backend/src/services/backup/backup-archive.service.d.ts.map +1 -0
  7. package/dist/backend/backend/src/services/backup/backup-archive.service.js +309 -0
  8. package/dist/backend/backend/src/services/backup/backup-archive.service.js.map +1 -0
  9. package/dist/backend/backend/src/services/backup/backup-cloud.client.d.ts +75 -0
  10. package/dist/backend/backend/src/services/backup/backup-cloud.client.d.ts.map +1 -0
  11. package/dist/backend/backend/src/services/backup/backup-cloud.client.js +134 -0
  12. package/dist/backend/backend/src/services/backup/backup-cloud.client.js.map +1 -0
  13. package/dist/backend/backend/src/services/backup/backup-restore.service.d.ts +78 -0
  14. package/dist/backend/backend/src/services/backup/backup-restore.service.d.ts.map +1 -0
  15. package/dist/backend/backend/src/services/backup/backup-restore.service.js +358 -0
  16. package/dist/backend/backend/src/services/backup/backup-restore.service.js.map +1 -0
  17. package/dist/backend/backend/src/services/backup/backup.types.d.ts +163 -0
  18. package/dist/backend/backend/src/services/backup/backup.types.d.ts.map +1 -0
  19. package/dist/backend/backend/src/services/backup/backup.types.js +13 -0
  20. package/dist/backend/backend/src/services/backup/backup.types.js.map +1 -0
  21. package/dist/backend/backend/src/services/cloud/cloud-sync.service.d.ts +29 -2
  22. package/dist/backend/backend/src/services/cloud/cloud-sync.service.d.ts.map +1 -1
  23. package/dist/backend/backend/src/services/cloud/cloud-sync.service.js +97 -13
  24. package/dist/backend/backend/src/services/cloud/cloud-sync.service.js.map +1 -1
  25. package/dist/cli/backend/src/constants.d.ts +22 -1
  26. package/dist/cli/backend/src/constants.d.ts.map +1 -1
  27. package/dist/cli/backend/src/constants.js +22 -1
  28. package/dist/cli/backend/src/constants.js.map +1 -1
  29. package/dist/cli/backend/src/controllers/cloud/cloud-google-auth.controller.d.ts +70 -0
  30. package/dist/cli/backend/src/controllers/cloud/cloud-google-auth.controller.d.ts.map +1 -0
  31. package/dist/cli/backend/src/controllers/cloud/cloud-google-auth.controller.js +427 -0
  32. package/dist/cli/backend/src/controllers/cloud/cloud-google-auth.controller.js.map +1 -0
  33. package/dist/cli/backend/src/services/backup/backup-archive.service.d.ts +90 -0
  34. package/dist/cli/backend/src/services/backup/backup-archive.service.d.ts.map +1 -0
  35. package/dist/cli/backend/src/services/backup/backup-archive.service.js +309 -0
  36. package/dist/cli/backend/src/services/backup/backup-archive.service.js.map +1 -0
  37. package/dist/cli/backend/src/services/backup/backup-cloud.client.d.ts +75 -0
  38. package/dist/cli/backend/src/services/backup/backup-cloud.client.d.ts.map +1 -0
  39. package/dist/cli/backend/src/services/backup/backup-cloud.client.js +134 -0
  40. package/dist/cli/backend/src/services/backup/backup-cloud.client.js.map +1 -0
  41. package/dist/cli/backend/src/services/backup/backup-restore.service.d.ts +78 -0
  42. package/dist/cli/backend/src/services/backup/backup-restore.service.d.ts.map +1 -0
  43. package/dist/cli/backend/src/services/backup/backup-restore.service.js +358 -0
  44. package/dist/cli/backend/src/services/backup/backup-restore.service.js.map +1 -0
  45. package/dist/cli/backend/src/services/backup/backup.types.d.ts +163 -0
  46. package/dist/cli/backend/src/services/backup/backup.types.d.ts.map +1 -0
  47. package/dist/cli/backend/src/services/backup/backup.types.js +13 -0
  48. package/dist/cli/backend/src/services/backup/backup.types.js.map +1 -0
  49. package/dist/cli/backend/src/services/cloud/cloud-client.service.d.ts +410 -0
  50. package/dist/cli/backend/src/services/cloud/cloud-client.service.d.ts.map +1 -0
  51. package/dist/cli/backend/src/services/cloud/cloud-client.service.js +863 -0
  52. package/dist/cli/backend/src/services/cloud/cloud-client.service.js.map +1 -0
  53. package/dist/cli/backend/src/services/cloud/cloud-sync.service.d.ts +292 -0
  54. package/dist/cli/backend/src/services/cloud/cloud-sync.service.d.ts.map +1 -0
  55. package/dist/cli/backend/src/services/cloud/cloud-sync.service.js +1093 -0
  56. package/dist/cli/backend/src/services/cloud/cloud-sync.service.js.map +1 -0
  57. package/dist/cli/backend/src/services/cloud/cloud-sync.types.d.ts +328 -0
  58. package/dist/cli/backend/src/services/cloud/cloud-sync.types.d.ts.map +1 -0
  59. package/dist/cli/backend/src/services/cloud/cloud-sync.types.js +171 -0
  60. package/dist/cli/backend/src/services/cloud/cloud-sync.types.js.map +1 -0
  61. package/dist/cli/backend/src/services/cloud/device-identity.service.d.ts +89 -0
  62. package/dist/cli/backend/src/services/cloud/device-identity.service.d.ts.map +1 -0
  63. package/dist/cli/backend/src/services/cloud/device-identity.service.js +148 -0
  64. package/dist/cli/backend/src/services/cloud/device-identity.service.js.map +1 -0
  65. package/dist/cli/backend/src/services/user/user-identity.service.d.ts +86 -0
  66. package/dist/cli/backend/src/services/user/user-identity.service.d.ts.map +1 -0
  67. package/dist/cli/backend/src/services/user/user-identity.service.js +190 -0
  68. package/dist/cli/backend/src/services/user/user-identity.service.js.map +1 -0
  69. package/dist/cli/cli/src/commands/backup.d.ts +31 -0
  70. package/dist/cli/cli/src/commands/backup.d.ts.map +1 -0
  71. package/dist/cli/cli/src/commands/backup.js +280 -0
  72. package/dist/cli/cli/src/commands/backup.js.map +1 -0
  73. package/dist/cli/cli/src/index.js +10 -0
  74. package/dist/cli/cli/src/index.js.map +1 -1
  75. 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"}