@powersync/service-core 1.11.3 → 1.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (107) hide show
  1. package/CHANGELOG.md +46 -0
  2. package/dist/auth/CachedKeyCollector.js +2 -7
  3. package/dist/auth/CachedKeyCollector.js.map +1 -1
  4. package/dist/auth/CompoundKeyCollector.js.map +1 -1
  5. package/dist/auth/KeyCollector.d.ts +2 -2
  6. package/dist/auth/KeyStore.js +32 -14
  7. package/dist/auth/KeyStore.js.map +1 -1
  8. package/dist/auth/RemoteJWKSCollector.d.ts +1 -0
  9. package/dist/auth/RemoteJWKSCollector.js +39 -16
  10. package/dist/auth/RemoteJWKSCollector.js.map +1 -1
  11. package/dist/auth/auth-index.d.ts +1 -0
  12. package/dist/auth/auth-index.js +1 -0
  13. package/dist/auth/auth-index.js.map +1 -1
  14. package/dist/auth/utils.d.ts +6 -0
  15. package/dist/auth/utils.js +97 -0
  16. package/dist/auth/utils.js.map +1 -0
  17. package/dist/entry/commands/compact-action.js +4 -1
  18. package/dist/entry/commands/compact-action.js.map +1 -1
  19. package/dist/entry/commands/migrate-action.js +4 -1
  20. package/dist/entry/commands/migrate-action.js.map +1 -1
  21. package/dist/entry/commands/test-connection-action.js +4 -1
  22. package/dist/entry/commands/test-connection-action.js.map +1 -1
  23. package/dist/routes/RouterEngine.d.ts +2 -0
  24. package/dist/routes/RouterEngine.js +15 -10
  25. package/dist/routes/RouterEngine.js.map +1 -1
  26. package/dist/routes/auth.d.ts +5 -16
  27. package/dist/routes/auth.js +6 -4
  28. package/dist/routes/auth.js.map +1 -1
  29. package/dist/routes/configure-fastify.d.ts +3 -21
  30. package/dist/routes/configure-fastify.js +3 -6
  31. package/dist/routes/configure-fastify.js.map +1 -1
  32. package/dist/routes/configure-rsocket.js +28 -14
  33. package/dist/routes/configure-rsocket.js.map +1 -1
  34. package/dist/routes/endpoints/admin.js.map +1 -1
  35. package/dist/routes/endpoints/checkpointing.d.ts +4 -28
  36. package/dist/routes/endpoints/checkpointing.js.map +1 -1
  37. package/dist/routes/endpoints/route-endpoints-index.d.ts +1 -0
  38. package/dist/routes/endpoints/route-endpoints-index.js +1 -0
  39. package/dist/routes/endpoints/route-endpoints-index.js.map +1 -1
  40. package/dist/routes/endpoints/socket-route.js +22 -8
  41. package/dist/routes/endpoints/socket-route.js.map +1 -1
  42. package/dist/routes/endpoints/sync-rules.js.map +1 -1
  43. package/dist/routes/endpoints/sync-stream.d.ts +2 -14
  44. package/dist/routes/endpoints/sync-stream.js +28 -9
  45. package/dist/routes/endpoints/sync-stream.js.map +1 -1
  46. package/dist/routes/route-register.js +10 -6
  47. package/dist/routes/route-register.js.map +1 -1
  48. package/dist/routes/router.d.ts +8 -7
  49. package/dist/routes/router.js.map +1 -1
  50. package/dist/runner/teardown.js +4 -1
  51. package/dist/runner/teardown.js.map +1 -1
  52. package/dist/sync/BucketChecksumState.d.ts +40 -18
  53. package/dist/sync/BucketChecksumState.js +120 -72
  54. package/dist/sync/BucketChecksumState.js.map +1 -1
  55. package/dist/sync/RequestTracker.d.ts +22 -1
  56. package/dist/sync/RequestTracker.js +51 -2
  57. package/dist/sync/RequestTracker.js.map +1 -1
  58. package/dist/sync/sync.d.ts +3 -5
  59. package/dist/sync/sync.js +47 -32
  60. package/dist/sync/sync.js.map +1 -1
  61. package/dist/system/ServiceContext.d.ts +19 -4
  62. package/dist/system/ServiceContext.js +20 -8
  63. package/dist/system/ServiceContext.js.map +1 -1
  64. package/dist/util/config/collectors/config-collector.js +4 -33
  65. package/dist/util/config/collectors/config-collector.js.map +1 -1
  66. package/dist/util/config/collectors/impl/yaml-env.d.ts +7 -0
  67. package/dist/util/config/collectors/impl/yaml-env.js +59 -0
  68. package/dist/util/config/collectors/impl/yaml-env.js.map +1 -0
  69. package/dist/util/config/compound-config-collector.js +18 -1
  70. package/dist/util/config/compound-config-collector.js.map +1 -1
  71. package/dist/util/config/types.d.ts +11 -0
  72. package/package.json +6 -7
  73. package/src/auth/CachedKeyCollector.ts +4 -6
  74. package/src/auth/CompoundKeyCollector.ts +2 -1
  75. package/src/auth/KeyCollector.ts +2 -2
  76. package/src/auth/KeyStore.ts +45 -20
  77. package/src/auth/RemoteJWKSCollector.ts +39 -16
  78. package/src/auth/auth-index.ts +1 -0
  79. package/src/auth/utils.ts +102 -0
  80. package/src/entry/commands/compact-action.ts +4 -1
  81. package/src/entry/commands/migrate-action.ts +4 -1
  82. package/src/entry/commands/test-connection-action.ts +4 -1
  83. package/src/routes/RouterEngine.ts +21 -11
  84. package/src/routes/auth.ts +7 -6
  85. package/src/routes/configure-fastify.ts +6 -8
  86. package/src/routes/configure-rsocket.ts +33 -18
  87. package/src/routes/endpoints/admin.ts +5 -5
  88. package/src/routes/endpoints/checkpointing.ts +2 -2
  89. package/src/routes/endpoints/route-endpoints-index.ts +1 -0
  90. package/src/routes/endpoints/socket-route.ts +27 -11
  91. package/src/routes/endpoints/sync-rules.ts +4 -4
  92. package/src/routes/endpoints/sync-stream.ts +34 -11
  93. package/src/routes/route-register.ts +10 -7
  94. package/src/routes/router.ts +11 -4
  95. package/src/runner/teardown.ts +5 -1
  96. package/src/sync/BucketChecksumState.ts +160 -75
  97. package/src/sync/RequestTracker.ts +70 -3
  98. package/src/sync/sync.ts +69 -46
  99. package/src/system/ServiceContext.ts +31 -12
  100. package/src/util/config/collectors/config-collector.ts +4 -40
  101. package/src/util/config/collectors/impl/yaml-env.ts +67 -0
  102. package/src/util/config/compound-config-collector.ts +22 -5
  103. package/src/util/config/types.ts +13 -0
  104. package/test/src/auth.test.ts +29 -11
  105. package/test/src/config.test.ts +72 -0
  106. package/test/src/sync/BucketChecksumState.test.ts +32 -18
  107. package/tsconfig.tsbuildinfo +1 -1
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Environment variables can be substituted into the YAML config
3
+ * when parsing if the environment variable name starts with this prefix.
4
+ * Attempting to substitute any other environment variable will throw an exception.
5
+ *
6
+ * Example of substitution:
7
+ * storage:
8
+ * type: mongodb
9
+ * uri: !env PS_MONGO_URI
10
+ */
11
+ const YAML_ENV_PREFIX = 'PS_';
12
+ /**
13
+ * Custom YAML tag which performs string environment variable substitution
14
+ * Allows for type casting string environment variables to boolean or number
15
+ * by using the syntax !env PS_MONGO_PORT::number or !env PS_USE_SUPABASE::boolean
16
+ */
17
+ export const YamlEnvTag = {
18
+ tag: '!env',
19
+ resolve(envName, onError) {
20
+ if (!envName.startsWith(YAML_ENV_PREFIX)) {
21
+ onError(`Attempting to substitute environment variable ${envName} is not allowed. Variables must start with "${YAML_ENV_PREFIX}"`);
22
+ return envName;
23
+ }
24
+ // allow type casting if the envName contains a type suffix
25
+ // e.g. PS_MONGO_PORT::number or PS_USE_SUPABASE::boolean
26
+ const [name, type = 'string'] = envName.split('::');
27
+ let value = process.env[name];
28
+ if (typeof value == 'undefined') {
29
+ onError(`Attempted to substitute environment variable "${envName}" which is undefined. Set this variable on the environment.`);
30
+ return envName;
31
+ }
32
+ switch (type) {
33
+ case 'string':
34
+ return value;
35
+ case 'number':
36
+ const numberValue = Number(value);
37
+ if (Number.isNaN(numberValue)) {
38
+ onError(`Environment variable "${envName}" is not a valid number. Got: "${value}".`);
39
+ return envName;
40
+ }
41
+ return numberValue;
42
+ case 'boolean':
43
+ if (value?.toLowerCase() == 'true') {
44
+ return true;
45
+ }
46
+ else if (value?.toLowerCase() == 'false') {
47
+ return false;
48
+ }
49
+ else {
50
+ onError(`Environment variable "${envName}" is not a boolean. Expected "true" or "false", got "${value}".`);
51
+ return envName;
52
+ }
53
+ default:
54
+ onError(`Environment variable "${envName}" has an invalid type suffix "${type}".`);
55
+ return envName;
56
+ }
57
+ }
58
+ };
59
+ //# sourceMappingURL=yaml-env.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"yaml-env.js","sourceRoot":"","sources":["../../../../../src/util/config/collectors/impl/yaml-env.ts"],"names":[],"mappings":"AAEA;;;;;;;;;GASG;AACH,MAAM,eAAe,GAAG,KAAK,CAAC;AAE9B;;;;GAIG;AACH,MAAM,CAAC,MAAM,UAAU,GAAmB;IACxC,GAAG,EAAE,MAAM;IACX,OAAO,CAAC,OAAe,EAAE,OAAgC;QACvD,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,eAAe,CAAC,EAAE,CAAC;YACzC,OAAO,CACL,iDAAiD,OAAO,+CAA+C,eAAe,GAAG,CAC1H,CAAC;YACF,OAAO,OAAO,CAAC;QACjB,CAAC;QAED,2DAA2D;QAC3D,yDAAyD;QACzD,MAAM,CAAC,IAAI,EAAE,IAAI,GAAG,QAAQ,CAAC,GAAG,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAEpD,IAAI,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QAE9B,IAAI,OAAO,KAAK,IAAI,WAAW,EAAE,CAAC;YAChC,OAAO,CACL,iDAAiD,OAAO,6DAA6D,CACtH,CAAC;YACF,OAAO,OAAO,CAAC;QACjB,CAAC;QAED,QAAQ,IAAI,EAAE,CAAC;YACb,KAAK,QAAQ;gBACX,OAAO,KAAK,CAAC;YACf,KAAK,QAAQ;gBACX,MAAM,WAAW,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC;gBAClC,IAAI,MAAM,CAAC,KAAK,CAAC,WAAW,CAAC,EAAE,CAAC;oBAC9B,OAAO,CAAC,yBAAyB,OAAO,kCAAkC,KAAK,IAAI,CAAC,CAAC;oBACrF,OAAO,OAAO,CAAC;gBACjB,CAAC;gBACD,OAAO,WAAW,CAAC;YACrB,KAAK,SAAS;gBACZ,IAAI,KAAK,EAAE,WAAW,EAAE,IAAI,MAAM,EAAE,CAAC;oBACnC,OAAO,IAAI,CAAC;gBACd,CAAC;qBAAM,IAAI,KAAK,EAAE,WAAW,EAAE,IAAI,OAAO,EAAE,CAAC;oBAC3C,OAAO,KAAK,CAAC;gBACf,CAAC;qBAAM,CAAC;oBACN,OAAO,CAAC,yBAAyB,OAAO,wDAAwD,KAAK,IAAI,CAAC,CAAC;oBAC3G,OAAO,OAAO,CAAC;gBACjB,CAAC;YACH;gBACE,OAAO,CAAC,yBAAyB,OAAO,iCAAiC,IAAI,IAAI,CAAC,CAAC;gBACnF,OAAO,OAAO,CAAC;QACnB,CAAC;IACH,CAAC;CACF,CAAC"}
@@ -3,10 +3,10 @@ import * as auth from '../../auth/auth-index.js';
3
3
  import { Base64ConfigCollector } from './collectors/impl/base64-config-collector.js';
4
4
  import { FallbackConfigCollector } from './collectors/impl/fallback-config-collector.js';
5
5
  import { FileSystemConfigCollector } from './collectors/impl/filesystem-config-collector.js';
6
+ import { DEFAULT_MAX_BUCKETS_PER_CONNECTION, DEFAULT_MAX_CONCURRENT_CONNECTIONS, DEFAULT_MAX_DATA_FETCH_CONCURRENCY, DEFAULT_MAX_PARAMETER_QUERY_RESULTS, DEFAULT_MAX_POOL_SIZE } from './defaults.js';
6
7
  import { Base64SyncRulesCollector } from './sync-rules/impl/base64-sync-rules-collector.js';
7
8
  import { FileSystemSyncRulesCollector } from './sync-rules/impl/filesystem-sync-rules-collector.js';
8
9
  import { InlineSyncRulesCollector } from './sync-rules/impl/inline-sync-rules-collector.js';
9
- import { DEFAULT_MAX_BUCKETS_PER_CONNECTION, DEFAULT_MAX_CONCURRENT_CONNECTIONS, DEFAULT_MAX_DATA_FETCH_CONCURRENCY, DEFAULT_MAX_PARAMETER_QUERY_RESULTS, DEFAULT_MAX_POOL_SIZE } from './defaults.js';
10
10
  const POWERSYNC_DEV_KID = 'powersync-dev';
11
11
  const DEFAULT_COLLECTOR_OPTIONS = {
12
12
  configCollectors: [new Base64ConfigCollector(), new FileSystemConfigCollector(), new FallbackConfigCollector()],
@@ -107,6 +107,23 @@ export class CompoundConfigCollector {
107
107
  disable_telemetry_sharing: baseConfig.telemetry?.disable_telemetry_sharing ?? false,
108
108
  internal_service_endpoint: baseConfig.telemetry?.internal_service_endpoint ?? 'https://pulse.journeyapps.com/v1/metrics'
109
109
  },
110
+ healthcheck: {
111
+ /**
112
+ * Default to legacy mode if no probes config is provided.
113
+ * If users provide a config, all options require explicit opt-in.
114
+ */
115
+ probes: baseConfig.healthcheck?.probes
116
+ ? {
117
+ use_filesystem: baseConfig.healthcheck.probes.use_filesystem ?? false,
118
+ use_http: baseConfig.healthcheck.probes.use_http ?? false,
119
+ use_legacy: baseConfig.healthcheck.probes.use_legacy ?? false
120
+ }
121
+ : {
122
+ use_filesystem: false,
123
+ use_http: false,
124
+ use_legacy: true
125
+ }
126
+ },
110
127
  api_parameters: {
111
128
  max_buckets_per_connection: baseConfig.api?.parameters?.max_buckets_per_connection ?? DEFAULT_MAX_BUCKETS_PER_CONNECTION,
112
129
  max_parameter_query_results: baseConfig.api?.parameters?.max_parameter_query_results ?? DEFAULT_MAX_PARAMETER_QUERY_RESULTS,
@@ -1 +1 @@
1
- {"version":3,"file":"compound-config-collector.js","sourceRoot":"","sources":["../../../src/util/config/compound-config-collector.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"compound-config-collector.js","sourceRoot":"","sources":["../../../src/util/config/compound-config-collector.ts"],"names":[],"mappings":"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"}
@@ -60,5 +60,16 @@ export type ResolvedPowerSyncConfig = {
60
60
  };
61
61
  /** Prefix for postgres replication slot names. May eventually be connection-specific. */
62
62
  slot_name_prefix: string;
63
+ healthcheck: {
64
+ probes: {
65
+ use_filesystem: boolean;
66
+ use_http: boolean;
67
+ /**
68
+ * @deprecated This maintains backwards compatibility with the legacy default.
69
+ * Explicit probe configuration should be used instead.
70
+ */
71
+ use_legacy: boolean;
72
+ };
73
+ };
63
74
  parameters: Record<string, number | string | boolean | null>;
64
75
  };
package/package.json CHANGED
@@ -5,7 +5,7 @@
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },
8
- "version": "1.11.3",
8
+ "version": "1.12.0",
9
9
  "main": "dist/index.js",
10
10
  "license": "FSL-1.1-Apache-2.0",
11
11
  "type": "module",
@@ -29,19 +29,18 @@
29
29
  "node-fetch": "^3.3.2",
30
30
  "ts-codec": "^1.3.0",
31
31
  "uri-js": "^4.4.1",
32
- "uuid": "^9.0.1",
32
+ "uuid": "^11.1.0",
33
33
  "winston": "^3.13.0",
34
34
  "yaml": "^2.3.2",
35
- "@powersync/lib-services-framework": "0.5.4",
35
+ "@powersync/lib-services-framework": "0.6.0",
36
36
  "@powersync/service-jsonbig": "0.17.10",
37
- "@powersync/service-rsocket-router": "0.0.21",
38
- "@powersync/service-sync-rules": "0.26.0",
39
- "@powersync/service-types": "0.10.0"
37
+ "@powersync/service-rsocket-router": "0.1.0",
38
+ "@powersync/service-sync-rules": "0.26.1",
39
+ "@powersync/service-types": "0.11.0"
40
40
  },
41
41
  "devDependencies": {
42
42
  "@types/async": "^3.2.24",
43
43
  "@types/lodash": "^4.17.5",
44
- "@types/uuid": "^9.0.4",
45
44
  "fastify": "4.23.2",
46
45
  "fastify-plugin": "^4.5.1"
47
46
  },
@@ -3,6 +3,8 @@ import timers from 'timers/promises';
3
3
  import { KeySpec } from './KeySpec.js';
4
4
  import { LeakyBucket } from './LeakyBucket.js';
5
5
  import { KeyCollector, KeyResult } from './KeyCollector.js';
6
+ import { AuthorizationError } from '@powersync/lib-services-framework';
7
+ import { mapAuthConfigError } from './utils.js';
6
8
 
7
9
  /**
8
10
  * Manages caching and refreshing for a key collector.
@@ -39,7 +41,7 @@ export class CachedKeyCollector implements KeyCollector {
39
41
  */
40
42
  private keyExpiry = 3600000;
41
43
 
42
- private currentErrors: jose.errors.JOSEError[] = [];
44
+ private currentErrors: AuthorizationError[] = [];
43
45
  /**
44
46
  * Indicates a "fatal" error that should be retried.
45
47
  */
@@ -103,11 +105,7 @@ export class CachedKeyCollector implements KeyCollector {
103
105
  } catch (e) {
104
106
  this.error = true;
105
107
  // No result - keep previous keys
106
- if (e instanceof jose.errors.JOSEError) {
107
- this.currentErrors = [e];
108
- } else {
109
- this.currentErrors = [new jose.errors.JOSEError(e.message ?? 'Failed to fetch keys')];
110
- }
108
+ this.currentErrors = [mapAuthConfigError(e)];
111
109
  }
112
110
  }
113
111
 
@@ -1,6 +1,7 @@
1
1
  import * as jose from 'jose';
2
2
  import { KeySpec } from './KeySpec.js';
3
3
  import { KeyCollector, KeyResult } from './KeyCollector.js';
4
+ import { AuthorizationError } from '@powersync/lib-services-framework';
4
5
 
5
6
  export class CompoundKeyCollector implements KeyCollector {
6
7
  private collectors: KeyCollector[];
@@ -15,7 +16,7 @@ export class CompoundKeyCollector implements KeyCollector {
15
16
 
16
17
  async getKeys(): Promise<KeyResult> {
17
18
  let keys: KeySpec[] = [];
18
- let errors: jose.errors.JOSEError[] = [];
19
+ let errors: AuthorizationError[] = [];
19
20
  const promises = this.collectors.map((collector) =>
20
21
  collector.getKeys().then((result) => {
21
22
  keys.push(...result.keys);
@@ -1,4 +1,4 @@
1
- import * as jose from 'jose';
1
+ import { AuthorizationError } from '@powersync/lib-services-framework';
2
2
  import { KeySpec } from './KeySpec.js';
3
3
 
4
4
  export interface KeyCollector {
@@ -22,6 +22,6 @@ export interface KeyCollector {
22
22
  }
23
23
 
24
24
  export interface KeyResult {
25
- errors: jose.errors.JOSEError[];
25
+ errors: AuthorizationError[];
26
26
  keys: KeySpec[];
27
27
  }
@@ -1,9 +1,10 @@
1
- import { logger } from '@powersync/lib-services-framework';
1
+ import { logger, errors, AuthorizationError, ErrorCode } from '@powersync/lib-services-framework';
2
2
  import * as jose from 'jose';
3
3
  import secs from '../util/secs.js';
4
4
  import { JwtPayload } from './JwtPayload.js';
5
5
  import { KeyCollector } from './KeyCollector.js';
6
6
  import { KeyOptions, KeySpec, SUPPORTED_ALGORITHMS } from './KeySpec.js';
7
+ import { mapAuthError } from './utils.js';
7
8
 
8
9
  /**
9
10
  * KeyStore to get keys and verify tokens.
@@ -49,7 +50,8 @@ export class KeyStore<Collector extends KeyCollector = KeyCollector> {
49
50
  clockTolerance: 60,
50
51
  // More specific algorithm checking is done when selecting the key to use.
51
52
  algorithms: SUPPORTED_ALGORITHMS,
52
- requiredClaims: ['aud', 'sub', 'iat', 'exp']
53
+ // 'aud' presence is checked below, so we can add more details to the error message.
54
+ requiredClaims: ['sub', 'iat', 'exp']
53
55
  });
54
56
 
55
57
  let audiences = options.defaultAudiences;
@@ -60,8 +62,12 @@ export class KeyStore<Collector extends KeyCollector = KeyCollector> {
60
62
 
61
63
  const tokenPayload = result.payload;
62
64
 
63
- let aud = tokenPayload.aud!;
64
- if (!Array.isArray(aud)) {
65
+ let aud = tokenPayload.aud;
66
+ if (aud == null) {
67
+ throw new AuthorizationError(ErrorCode.PSYNC_S2105, `JWT payload is missing a required claim "aud"`, {
68
+ configurationDetails: `Current configuration allows these audience values: ${JSON.stringify(audiences)}`
69
+ });
70
+ } else if (!Array.isArray(aud)) {
65
71
  aud = [aud];
66
72
  }
67
73
  if (
@@ -69,7 +75,11 @@ export class KeyStore<Collector extends KeyCollector = KeyCollector> {
69
75
  return audiences.includes(a);
70
76
  })
71
77
  ) {
72
- throw new jose.errors.JWTClaimValidationFailed('unexpected "aud" claim value', 'aud', 'check_failed');
78
+ throw new AuthorizationError(
79
+ ErrorCode.PSYNC_S2105,
80
+ `Unexpected "aud" claim value: ${JSON.stringify(tokenPayload.aud)}`,
81
+ { configurationDetails: `Current configuration allows these audience values: ${JSON.stringify(audiences)}` }
82
+ );
73
83
  }
74
84
 
75
85
  const tokenDuration = tokenPayload.exp! - tokenPayload.iat!;
@@ -78,12 +88,15 @@ export class KeyStore<Collector extends KeyCollector = KeyCollector> {
78
88
  // is too far into the future.
79
89
  const maxAge = keyOptions.maxLifetimeSeconds ?? secs(options.maxAge);
80
90
  if (tokenDuration > maxAge) {
81
- throw new jose.errors.JWTInvalid(`Token must expire in a maximum of ${maxAge} seconds, got ${tokenDuration}`);
91
+ throw new AuthorizationError(
92
+ ErrorCode.PSYNC_S2104,
93
+ `Token must expire in a maximum of ${maxAge} seconds, got ${tokenDuration}s`
94
+ );
82
95
  }
83
96
 
84
97
  const parameters = tokenPayload.parameters;
85
98
  if (parameters != null && (Array.isArray(parameters) || typeof parameters != 'object')) {
86
- throw new jose.errors.JWTInvalid('parameters must be an object');
99
+ throw new AuthorizationError(ErrorCode.PSYNC_S2101, `Payload parameters must be an object`);
87
100
  }
88
101
 
89
102
  return tokenPayload as JwtPayload;
@@ -91,16 +104,20 @@ export class KeyStore<Collector extends KeyCollector = KeyCollector> {
91
104
 
92
105
  private async verifyInternal(token: string, options: jose.JWTVerifyOptions) {
93
106
  let keyOptions: KeyOptions | undefined = undefined;
94
- const result = await jose.jwtVerify(
95
- token,
96
- async (header) => {
97
- let key = await this.getCachedKey(token, header);
98
- keyOptions = key.options;
99
- return key.key;
100
- },
101
- options
102
- );
103
- return { result, keyOptions: keyOptions! };
107
+ try {
108
+ const result = await jose.jwtVerify(
109
+ token,
110
+ async (header) => {
111
+ let key = await this.getCachedKey(token, header);
112
+ keyOptions = key.options;
113
+ return key.key;
114
+ },
115
+ options
116
+ );
117
+ return { result, keyOptions: keyOptions! };
118
+ } catch (e) {
119
+ throw mapAuthError(e, token);
120
+ }
104
121
  }
105
122
 
106
123
  private async getCachedKey(token: string, header: jose.JWTHeaderParameters): Promise<KeySpec> {
@@ -112,7 +129,10 @@ export class KeyStore<Collector extends KeyCollector = KeyCollector> {
112
129
  for (let key of keys) {
113
130
  if (key.kid == kid) {
114
131
  if (!key.matchesAlgorithm(header.alg)) {
115
- throw new jose.errors.JOSEAlgNotAllowed(`Unexpected token algorithm ${header.alg}`);
132
+ throw new AuthorizationError(ErrorCode.PSYNC_S2101, `Unexpected token algorithm ${header.alg}`, {
133
+ configurationDetails: `Key kid: ${key.source.kid}, alg: ${key.source.alg}, kty: ${key.source.kty}`
134
+ // Token details automatically populated elsewhere
135
+ });
116
136
  }
117
137
  return key;
118
138
  }
@@ -145,8 +165,13 @@ export class KeyStore<Collector extends KeyCollector = KeyCollector> {
145
165
  logger.error(`Failed to refresh keys`, e);
146
166
  });
147
167
 
148
- throw new jose.errors.JOSEError(
149
- 'Could not find an appropriate key in the keystore. The key is missing or no key matched the token KID'
168
+ throw new AuthorizationError(
169
+ ErrorCode.PSYNC_S2101,
170
+ 'Could not find an appropriate key in the keystore. The key is missing or no key matched the token KID',
171
+ {
172
+ configurationDetails: `Known kid values: ${keys.map((key) => key.kid ?? '*').join(', ')}`
173
+ // tokenDetails automatically populated later
174
+ }
150
175
  );
151
176
  }
152
177
  }
@@ -4,6 +4,7 @@ import * as jose from 'jose';
4
4
  import fetch from 'node-fetch';
5
5
 
6
6
  import {
7
+ AuthorizationError,
7
8
  ErrorCode,
8
9
  LookupOptions,
9
10
  makeHostnameLookupFunction,
@@ -46,28 +47,43 @@ export class RemoteJWKSCollector implements KeyCollector {
46
47
  this.agent = this.resolveAgent();
47
48
  }
48
49
 
49
- async getKeys(): Promise<KeyResult> {
50
+ private async getJwksData(): Promise<any> {
50
51
  const abortController = new AbortController();
51
52
  const timeout = setTimeout(() => {
52
53
  abortController.abort();
53
54
  }, 30_000);
54
55
 
55
- const res = await fetch(this.url, {
56
- method: 'GET',
57
- headers: {
58
- Accept: 'application/json'
59
- },
60
- signal: abortController.signal,
61
- agent: this.agent
62
- });
63
-
64
- if (!res.ok) {
65
- throw new jose.errors.JWKSInvalid(`JWKS request failed with ${res.statusText}`);
66
- }
56
+ try {
57
+ const res = await fetch(this.url, {
58
+ method: 'GET',
59
+ headers: {
60
+ Accept: 'application/json'
61
+ },
62
+ signal: abortController.signal,
63
+ agent: this.agent
64
+ });
65
+
66
+ if (!res.ok) {
67
+ throw new AuthorizationError(ErrorCode.PSYNC_S2204, `JWKS request failed with ${res.statusText}`, {
68
+ configurationDetails: `JWKS URL: ${this.url}`
69
+ });
70
+ }
67
71
 
68
- const data = (await res.json()) as any;
72
+ return (await res.json()) as any;
73
+ } catch (e) {
74
+ throw new AuthorizationError(ErrorCode.PSYNC_S2204, `JWKS request failed`, {
75
+ configurationDetails: `JWKS URL: ${this.url}`,
76
+ // This covers most cases of FetchError
77
+ // `cause: e` could lose the error message
78
+ cause: { message: e.message, code: e.code }
79
+ });
80
+ } finally {
81
+ clearTimeout(timeout);
82
+ }
83
+ }
69
84
 
70
- clearTimeout(timeout);
85
+ async getKeys(): Promise<KeyResult> {
86
+ const data = await this.getJwksData();
71
87
 
72
88
  // https://github.com/panva/jose/blob/358e864a0cccf1e0f9928a959f91f18f3f06a7de/src/jwks/local.ts#L36
73
89
  if (
@@ -75,7 +91,14 @@ export class RemoteJWKSCollector implements KeyCollector {
75
91
  !Array.isArray(data.keys) ||
76
92
  !(data.keys as any[]).every((key) => typeof key == 'object' && !Array.isArray(key))
77
93
  ) {
78
- return { keys: [], errors: [new jose.errors.JWKSInvalid(`No keys in found in JWKS response`)] };
94
+ return {
95
+ keys: [],
96
+ errors: [
97
+ new AuthorizationError(ErrorCode.PSYNC_S2204, `Invalid JWKS response`, {
98
+ configurationDetails: `JWKS URL: ${this.url}. Response:\n${JSON.stringify(data, null, 2)}`
99
+ })
100
+ ]
101
+ };
79
102
  }
80
103
 
81
104
  let keys: KeySpec[] = [];
@@ -8,3 +8,4 @@ export * from './LeakyBucket.js';
8
8
  export * from './RemoteJWKSCollector.js';
9
9
  export * from './StaticKeyCollector.js';
10
10
  export * from './StaticSupabaseKeyCollector.js';
11
+ export * from './utils.js';
@@ -0,0 +1,102 @@
1
+ import { AuthorizationError, ErrorCode } from '@powersync/lib-services-framework';
2
+ import * as jose from 'jose';
3
+
4
+ export function mapJoseError(error: jose.errors.JOSEError, token: string): AuthorizationError {
5
+ const tokenDetails = tokenDebugDetails(token);
6
+ if (error.code === jose.errors.JWSInvalid.code || error.code === jose.errors.JWTInvalid.code) {
7
+ return new AuthorizationError(ErrorCode.PSYNC_S2101, 'Token is not a well-formed JWT. Check the token format.', {
8
+ tokenDetails,
9
+ cause: error
10
+ });
11
+ } else if (error.code === jose.errors.JWTClaimValidationFailed.code) {
12
+ // Jose message: missing required "sub" claim
13
+ const claim = (error as jose.errors.JWTClaimValidationFailed).claim;
14
+ return new AuthorizationError(
15
+ ErrorCode.PSYNC_S2101,
16
+ `JWT payload is missing a required claim ${JSON.stringify(claim)}`,
17
+ {
18
+ cause: error,
19
+ tokenDetails
20
+ }
21
+ );
22
+ } else if (error.code == jose.errors.JWTExpired.code) {
23
+ // Jose message: "exp" claim timestamp check failed
24
+ return new AuthorizationError(ErrorCode.PSYNC_S2103, `JWT has expired`, {
25
+ cause: error,
26
+ tokenDetails
27
+ });
28
+ }
29
+ return new AuthorizationError(ErrorCode.PSYNC_S2101, error.message, { cause: error });
30
+ }
31
+
32
+ export function mapAuthError(error: any, token: string): AuthorizationError {
33
+ if (error instanceof AuthorizationError) {
34
+ error.tokenDetails ??= tokenDebugDetails(token);
35
+ return error;
36
+ } else if (error instanceof jose.errors.JOSEError) {
37
+ return mapJoseError(error, token);
38
+ }
39
+ return new AuthorizationError(ErrorCode.PSYNC_S2101, error.message, {
40
+ cause: error,
41
+ tokenDetails: tokenDebugDetails(token)
42
+ });
43
+ }
44
+
45
+ export function mapJoseConfigError(error: jose.errors.JOSEError): AuthorizationError {
46
+ return new AuthorizationError(ErrorCode.PSYNC_S2201, error.message ?? 'Authorization error', { cause: error });
47
+ }
48
+
49
+ export function mapAuthConfigError(error: any): AuthorizationError {
50
+ if (error instanceof AuthorizationError) {
51
+ return error;
52
+ } else if (error instanceof jose.errors.JOSEError) {
53
+ return mapJoseConfigError(error);
54
+ }
55
+ return new AuthorizationError(ErrorCode.PSYNC_S2201, error.message ?? 'Auth configuration error', { cause: error });
56
+ }
57
+
58
+ /**
59
+ * Decode token for debugging purposes.
60
+ *
61
+ * We use this to add details to our logs. We don't log the entire token, since it may for example
62
+ * a password incorrectly used as a token.
63
+ */
64
+ function tokenDebugDetails(token: string): string {
65
+ try {
66
+ // For valid tokens, we return the header and payload
67
+ const header = jose.decodeProtectedHeader(token);
68
+ const payload = jose.decodeJwt(token);
69
+ return `<header: ${JSON.stringify(header)} payload: ${JSON.stringify(payload)}>`;
70
+ } catch (e) {
71
+ // Token fails to parse. Return some details.
72
+ return invalidTokenDetails(token);
73
+ }
74
+ }
75
+
76
+ function invalidTokenDetails(token: string): string {
77
+ const parts = token.split('.');
78
+ if (parts.length !== 3) {
79
+ return `<token with ${parts.length} parts (needs 3), length=${token.length}>`;
80
+ }
81
+
82
+ const [headerB64, payloadB64, signatureB64] = parts;
83
+
84
+ try {
85
+ JSON.parse(Buffer.from(headerB64, 'base64url').toString('utf8'));
86
+ } catch (e) {
87
+ return `<token with unparsable header>`;
88
+ }
89
+
90
+ try {
91
+ JSON.parse(Buffer.from(payloadB64, 'base64url').toString('utf8'));
92
+ } catch (e) {
93
+ return `<token with unparsable payload>`;
94
+ }
95
+ try {
96
+ Buffer.from(signatureB64, 'base64url');
97
+ } catch (e) {
98
+ return `<token with unparsable signature>`;
99
+ }
100
+
101
+ return `<invalid JWT, length=${token.length}>`;
102
+ }
@@ -37,7 +37,10 @@ export function registerCompactAction(program: Command) {
37
37
  logger.info(`Compacting storage for ${buckets?.join(', ')}...`);
38
38
  }
39
39
  const config = await utils.loadConfig(extractRunnerOptions(options));
40
- const serviceContext = new system.ServiceContextContainer(config);
40
+ const serviceContext = new system.ServiceContextContainer({
41
+ serviceMode: system.ServiceContextMode.COMPACT,
42
+ configuration: config
43
+ });
41
44
 
42
45
  // Register modules in order to allow custom module compacting
43
46
  const moduleManager = container.getImplementation(modules.ModuleManager);
@@ -18,7 +18,10 @@ export function registerMigrationAction(program: Command) {
18
18
  .argument('<direction>', 'Migration direction. `up` or `down`')
19
19
  .action(async (direction: migrations.Direction, options) => {
20
20
  const config = await utils.loadConfig(extractRunnerOptions(options));
21
- const serviceContext = new system.ServiceContextContainer(config);
21
+ const serviceContext = new system.ServiceContextContainer({
22
+ serviceMode: system.ServiceContextMode.MIGRATION,
23
+ configuration: config
24
+ });
22
25
 
23
26
  // Register modules in order to allow custom module migrations
24
27
  const moduleManager = container.getImplementation(modules.ModuleManager);
@@ -17,7 +17,10 @@ export function registerTestConnectionAction(program: Command) {
17
17
  return testConnectionCommand.description('Test connection').action(async (options) => {
18
18
  try {
19
19
  const config = await utils.loadConfig(extractRunnerOptions(options));
20
- const serviceContext = new system.ServiceContextContainer(config);
20
+ const serviceContext = new system.ServiceContextContainer({
21
+ serviceMode: system.ServiceContextMode.TEST_CONNECTION,
22
+ configuration: config
23
+ });
21
24
 
22
25
  const replication = new ReplicationEngine();
23
26
  serviceContext.register(ReplicationEngine, replication);