@thirdweb-dev/service-utils 0.0.0-dev-f2d9bcd-20230713055621 → 0.0.0-dev-2666fdc-20230713214856

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.
@@ -1,21 +1,6 @@
1
- /// <reference types="node" />
2
- import { AuthorizationOptions, AuthorizationResponse, AuthorizationValidations } from "./types";
3
- import { KVNamespace, ExecutionContext } from "@cloudflare/workers-types";
4
- import { IncomingHttpHeaders } from "http";
5
- interface AuthorizeWorkerOptions {
6
- ctx?: ExecutionContext;
7
- kvStore: KVNamespace<string>;
8
- headers: Headers;
9
- clientId: string;
10
- authOpts: AuthorizationOptions;
11
- validations?: AuthorizationValidations;
12
- }
13
- export declare function authorizeWorkerService(options: AuthorizeWorkerOptions): Promise<AuthorizationResponse>;
14
- export declare function authorizeNodeService(options: {
15
- clientId: string;
16
- headers: IncomingHttpHeaders;
17
- authOpts: AuthorizationOptions;
18
- validations?: AuthorizationValidations;
19
- }): Promise<AuthorizationResponse>;
20
- export {};
1
+ import { AuthorizationResponse, AuthorizeCFWorkerOptions, AuthorizeNodeServiceOptions } from "./types";
2
+ export declare function authorizeCFWorkerService(options: AuthorizeCFWorkerOptions): Promise<AuthorizationResponse>;
3
+ export declare function authorizeNodeService(options: AuthorizeNodeServiceOptions): Promise<AuthorizationResponse>;
4
+ export declare function hashSecret(secret: string): string;
5
+ export declare function hashClientId(secret: string): string;
21
6
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"../../../../src/auth","sources":["index.ts"],"names":[],"mappings":";AACA,OAAO,EAGL,oBAAoB,EACpB,qBAAqB,EACrB,wBAAwB,EACzB,MAAM,SAAS,CAAC;AACjB,OAAO,EAAE,WAAW,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AAC1E,OAAO,EAAE,mBAAmB,EAAE,MAAM,MAAM,CAAC;AAE3C,UAAU,sBAAsB;IAC9B,GAAG,CAAC,EAAE,gBAAgB,CAAC;IACvB,OAAO,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IAC7B,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,oBAAoB,CAAC;IAC/B,WAAW,CAAC,EAAE,wBAAwB,CAAC;CACxC;AAED,wBAAsB,sBAAsB,CAAC,OAAO,EAAE,sBAAsB,kCAqD3E;AAwLD,wBAAsB,oBAAoB,CAAC,OAAO,EAAE;IAClD,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,mBAAmB,CAAC;IAC7B,QAAQ,EAAE,oBAAoB,CAAC;IAC/B,WAAW,CAAC,EAAE,wBAAwB,CAAC;CACxC,kCAmCA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"../../../../src/auth","sources":["index.ts"],"names":[],"mappings":"AACA,OAAO,EAIL,qBAAqB,EAErB,wBAAwB,EACxB,2BAA2B,EAE5B,MAAM,SAAS,CAAC;AAGjB,wBAAsB,wBAAwB,CAC5C,OAAO,EAAE,wBAAwB,kCAkClC;AAED,wBAAsB,oBAAoB,CACxC,OAAO,EAAE,2BAA2B,kCASrC;AAED,wBAAgB,UAAU,CAAC,MAAM,EAAE,MAAM,UAExC;AAED,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,UAG1C"}
@@ -1,18 +1,34 @@
1
+ import { KVNamespace, ExecutionContext } from "@cloudflare/workers-types";
1
2
  import { ServiceName } from "../types";
2
- export interface AuthorizationOptions {
3
+ export interface AuthOptions {
4
+ clientId: string;
5
+ secretHash?: string;
6
+ bundleId?: string;
7
+ origin?: string;
8
+ }
9
+ export interface AuthorizeCFWorkerOptions {
10
+ ctx: ExecutionContext;
11
+ kvStore: KVNamespace<string>;
12
+ authOptions: AuthOptions;
13
+ serviceConfig: ServiceConfiguration;
14
+ validations: AuthorizationValidations;
15
+ }
16
+ export interface AuthorizeNodeServiceOptions {
17
+ authOptions: AuthOptions;
18
+ serviceConfig: ServiceConfiguration;
19
+ validations: AuthorizationValidations;
20
+ }
21
+ export interface ServiceConfiguration {
3
22
  apiUrl: string;
4
- serviceAPIKey: string;
5
23
  scope: ServiceName;
6
- origin?: string;
24
+ serviceKey: string;
7
25
  cachedKey?: ApiKey;
8
26
  cacheTtl?: number;
9
27
  onRefetchComplete?: (key: ApiKey) => void;
10
28
  }
11
29
  export interface AuthorizationValidations {
12
30
  serviceTargetAddresses?: string[];
13
- serviceActions?: string[];
14
- domains: string[];
15
- bundleIds: string[];
31
+ serviceAction?: string;
16
32
  }
17
33
  export interface AuthorizationResponse {
18
34
  authorized: boolean;
@@ -32,14 +48,14 @@ export interface ApiResponse {
32
48
  export interface ApiKey {
33
49
  id: string;
34
50
  key: string;
51
+ secretHash: string;
35
52
  walletAddresses: string[];
36
53
  domains: string[];
37
- services?: [
38
- {
39
- name: string;
40
- targetAddresses: string[];
41
- actions: string[];
42
- }
43
- ];
54
+ bundleIds: string[];
55
+ services: {
56
+ name: string;
57
+ targetAddresses: string[];
58
+ actions: string[];
59
+ }[];
44
60
  }
45
61
  //# sourceMappingURL=types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"../../../../src/auth","sources":["types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAEvC,MAAM,WAAW,oBAAoB;IACnC,MAAM,EAAE,MAAM,CAAC;IACf,aAAa,EAAE,MAAM,CAAC;IACtB,KAAK,EAAE,WAAW,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,iBAAiB,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;CAC3C;AAED,MAAM,WAAW,wBAAwB;IACvC,sBAAsB,CAAC,EAAE,MAAM,EAAE,CAAC;IAClC,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,SAAS,EAAE,MAAM,EAAE,CAAC;CACrB;AAED,MAAM,WAAW,qBAAqB;IACpC,UAAU,EAAE,OAAO,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,KAAK,EAAE;QACL,IAAI,EAAE,MAAM,CAAC;QACb,UAAU,EAAE,MAAM,CAAC;QACnB,OAAO,EAAE,MAAM,CAAC;KACjB,CAAC;CACH;AAED,MAAM,WAAW,MAAM;IACrB,EAAE,EAAE,MAAM,CAAC;IACX,GAAG,EAAE,MAAM,CAAC;IACZ,eAAe,EAAE,MAAM,EAAE,CAAC;IAC1B,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,QAAQ,CAAC,EAAE;QACT;YACE,IAAI,EAAE,MAAM,CAAC;YACb,eAAe,EAAE,MAAM,EAAE,CAAC;YAC1B,OAAO,EAAE,MAAM,EAAE,CAAC;SACnB;KACF,CAAC;CACH"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"../../../../src/auth","sources":["types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AAC1E,OAAO,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAEvC,MAAM,WAAW,WAAW;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,wBAAwB;IACvC,GAAG,EAAE,gBAAgB,CAAC;IACtB,OAAO,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IAC7B,WAAW,EAAE,WAAW,CAAC;IACzB,aAAa,EAAE,oBAAoB,CAAC;IACpC,WAAW,EAAE,wBAAwB,CAAC;CACvC;AAED,MAAM,WAAW,2BAA2B;IAC1C,WAAW,EAAE,WAAW,CAAC;IACzB,aAAa,EAAE,oBAAoB,CAAC;IACpC,WAAW,EAAE,wBAAwB,CAAC;CACvC;AAED,MAAM,WAAW,oBAAoB;IACnC,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,WAAW,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,iBAAiB,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;CAC3C;AAED,MAAM,WAAW,wBAAwB;IACvC,sBAAsB,CAAC,EAAE,MAAM,EAAE,CAAC;IAClC,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,qBAAqB;IACpC,UAAU,EAAE,OAAO,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,KAAK,EAAE;QACL,IAAI,EAAE,MAAM,CAAC;QACb,UAAU,EAAE,MAAM,CAAC;QACnB,OAAO,EAAE,MAAM,CAAC;KACjB,CAAC;CACH;AAED,MAAM,WAAW,MAAM;IACrB,EAAE,EAAE,MAAM,CAAC;IACX,GAAG,EAAE,MAAM,CAAC;IACZ,UAAU,EAAE,MAAM,CAAC;IACnB,eAAe,EAAE,MAAM,EAAE,CAAC;IAC1B,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,QAAQ,EAAE;QACR,IAAI,EAAE,MAAM,CAAC;QACb,eAAe,EAAE,MAAM,EAAE,CAAC;QAC1B,OAAO,EAAE,MAAM,EAAE,CAAC;KACnB,EAAE,CAAC;CACL"}
@@ -1,6 +1,4 @@
1
- import { Service } from "./types";
2
- export declare const SERVICES: Service[];
3
- export declare function getServiceByName(name: string): Service | undefined;
1
+ export declare function getServiceByName(name: string): import("./types").Service | undefined;
4
2
  export * from "./types";
5
3
  export * from "./auth";
6
4
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"../../../src","sources":["index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAElC,eAAO,MAAM,QAAQ,EAAE,OAAO,EAgC7B,CAAC;AAEF,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,GAAG,SAAS,CAElE;AAED,cAAc,SAAS,CAAC;AACxB,cAAc,QAAQ,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"../../../src","sources":["index.ts"],"names":[],"mappings":"AAEA,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,yCAE5C;AAED,cAAc,SAAS,CAAC;AACxB,cAAc,QAAQ,CAAC"}
@@ -1,4 +1,6 @@
1
- export type ServiceName = "bundler" | "rpc" | "storage";
1
+ export declare const SERVICE_NAMES: readonly ["bundler", "rpc", "storage"];
2
+ export declare const SERVICES: Service[];
3
+ export type ServiceName = (typeof SERVICE_NAMES)[number];
2
4
  export interface ServiceAction {
3
5
  name: string;
4
6
  title: string;
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"../../../src","sources":["types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,WAAW,GAAG,SAAS,GAAG,KAAK,GAAG,SAAS,CAAC;AAExD,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,OAAO;IACtB,IAAI,EAAE,WAAW,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,aAAa,EAAE,CAAC;CAC1B"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"../../../src","sources":["types.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,aAAa,wCAAyC,CAAC;AAEpE,eAAO,MAAM,QAAQ,EAAE,OAAO,EAgC7B,CAAC;AAEF,MAAM,MAAM,WAAW,GAAG,CAAC,OAAO,aAAa,CAAC,CAAC,MAAM,CAAC,CAAC;AAEzD,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,OAAO;IACtB,IAAI,EAAE,WAAW,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,aAAa,EAAE,CAAC;CAC1B"}
@@ -3,81 +3,121 @@
3
3
  Object.defineProperty(exports, '__esModule', { value: true });
4
4
 
5
5
  var fetch = require('isomorphic-unfetch');
6
+ var crypto = require('crypto');
6
7
 
7
8
  function _interopDefault (e) { return e && e.__esModule ? e : { 'default': e }; }
8
9
 
9
10
  var fetch__default = /*#__PURE__*/_interopDefault(fetch);
10
11
 
11
- async function authorizeWorkerService(options) {
12
+ const SERVICE_NAMES = ["bundler", "rpc", "storage"];
13
+ const SERVICES = [{
14
+ name: "storage",
15
+ title: "Storage",
16
+ description: "IPFS Upload and Download",
17
+ actions: [{
18
+ name: "read",
19
+ title: "Download",
20
+ description: "Download a file from Storage"
21
+ }, {
22
+ name: "write",
23
+ title: "Upload",
24
+ description: "Upload a file to Storage"
25
+ }]
26
+ }, {
27
+ name: "rpc",
28
+ title: "RPC",
29
+ description: "Accelerated RPC Edge",
30
+ // all actions allowed
31
+ actions: []
32
+ }, {
33
+ name: "bundler",
34
+ title: "Smart Wallets",
35
+ description: "Bundler & Paymaster services",
36
+ // all actions allowed
37
+ actions: []
38
+ }];
39
+
40
+ async function authorizeCFWorkerService(options) {
41
+ const {
42
+ kvStore,
43
+ ctx,
44
+ authOptions,
45
+ serviceConfig,
46
+ validations
47
+ } = options;
48
+ const {
49
+ clientId
50
+ } = authOptions;
12
51
  let cachedKey;
13
- if (!options.clientId) {
14
- return {
15
- authorized: false,
16
- errorMessage: "The ClientId is missing. Make sure it is included with your Authorization Bearer request header.",
17
- errorCode: "MISSING_CLIENT_ID",
18
- statusCode: 422
19
- };
20
- }
21
52
 
22
53
  // first, check if the key is in KV
23
54
  try {
24
- const kvKey = await options.kvStore.get(options.clientId);
55
+ const kvKey = await kvStore.get(clientId);
25
56
  if (kvKey) {
26
57
  cachedKey = JSON.parse(kvKey);
27
58
  }
28
59
  } catch (err) {
29
60
  // ignore JSON parse, assuming not valid
30
61
  }
31
- const origin = options.headers.get("Origin") || "";
32
- let originHost;
33
- if (origin) {
34
- try {
35
- const originUrl = new URL(origin);
36
- originHost = originUrl.host;
37
- } catch (error) {
38
- // ignore, will be verified by domains
39
- }
40
- }
41
62
  const updateKv = async keyData => {
42
- options.kvStore.put(options.clientId, JSON.stringify(keyData), {
43
- expirationTtl: options.authOpts.cacheTtl || 60
63
+ kvStore.put(clientId, JSON.stringify(keyData), {
64
+ expirationTtl: serviceConfig.cacheTtl || 60
44
65
  });
45
66
  };
46
- return authorize(options.clientId, {
47
- ...options.authOpts,
48
- origin: originHost,
49
- cachedKey,
50
- onRefetchComplete: keyData => {
51
- options?.ctx?.waitUntil(updateKv(keyData));
52
- }
53
- }, options.validations);
67
+ return authorize({
68
+ authOptions,
69
+ serviceConfig: {
70
+ ...serviceConfig,
71
+ cachedKey,
72
+ onRefetchComplete: keyData => {
73
+ ctx.waitUntil(updateKv(keyData));
74
+ }
75
+ },
76
+ validations
77
+ });
78
+ }
79
+ async function authorizeNodeService(options) {
80
+ const {
81
+ authOptions,
82
+ serviceConfig,
83
+ validations
84
+ } = options;
85
+ return authorize({
86
+ authOptions,
87
+ serviceConfig,
88
+ validations
89
+ });
90
+ }
91
+ function hashSecret(secret) {
92
+ return crypto.createHash("sha256").update(secret).digest("hex");
93
+ }
94
+ function hashClientId(secret) {
95
+ const hashed = crypto.createHash("sha256").update(secret).digest("hex");
96
+ return hashed.slice(0, 32);
54
97
  }
55
98
 
56
99
  /**
57
- * Authorizes a request for a given clientId
58
- *
59
- * @param clientId The String client id
60
- * @params authOpts The Object auth options
61
- * origin - The String origin
62
- * apiUrl - The String API URL
63
- * scope - The ServiceName scope identifier
64
- * cachedKey - The ApiKey (optional) cached key
65
- * onRefetchComplete - The Func to trigger after key refetch from API
66
- * @params validations The Object of validations to run on a key
67
- * serviceTargetAddresses - The Array (optional) of service target addresses to validate
68
- * serviceActions - The Array (optional) of service actions to validate
100
+ * Authorizes a request for a given client ID
69
101
  *
70
102
  * @returns The Promise AuthorizationResponse
71
103
  */
72
- async function authorize(clientId, authOpts, validations) {
104
+ async function authorize(options) {
73
105
  try {
106
+ const {
107
+ authOptions,
108
+ serviceConfig,
109
+ validations
110
+ } = options;
111
+ const {
112
+ clientId
113
+ } = authOptions;
74
114
  const {
75
115
  apiUrl,
76
- origin,
77
116
  scope,
117
+ serviceKey,
78
118
  cachedKey,
79
119
  onRefetchComplete
80
- } = authOpts;
120
+ } = serviceConfig;
81
121
  let keyData = cachedKey;
82
122
 
83
123
  // no cached key, re-fetch from API
@@ -85,8 +125,8 @@ async function authorize(clientId, authOpts, validations) {
85
125
  const response = await fetch__default["default"](`${apiUrl}/v1/keys/use/?scope=${scope}&clientId=${clientId}`, {
86
126
  method: "GET",
87
127
  headers: {
88
- "content-type": "application/json",
89
- "x-service-api-key": authOpts.serviceAPIKey
128
+ "x-service-api-key": serviceKey,
129
+ "content-type": "application/json"
90
130
  }
91
131
  });
92
132
  const apiResponse = await response.json();
@@ -110,107 +150,22 @@ async function authorize(clientId, authOpts, validations) {
110
150
  //
111
151
  // Run validations
112
152
  //
113
- const {
114
- serviceActions,
115
- serviceTargetAddresses
116
- } = validations || {};
117
-
118
- // validate domains
119
- if (keyData.domains && keyData.domains?.length > 0) {
120
- let originHost = "";
121
- if (origin) {
122
- try {
123
- const originUrl = new URL(origin);
124
- originHost = originUrl.host;
125
- } catch (error) {
126
- // ignore, will be verified by domains
127
- }
128
- }
129
- if (
130
- // find matching domain, or if all domains allowed
131
- !keyData.domains.find(d => {
132
- if (d === "*") {
133
- return true;
134
- }
135
-
136
- // If the allowedDomain has a wildcard,
137
- // we'll check that the ending of our domain matches the wildcard
138
- if (d.startsWith("*.")) {
139
- const wildcard = d.slice(2);
140
- return originHost.endsWith(wildcard);
141
- }
142
-
143
- // If there's no wildcard, we'll check for an exact match
144
- return d === originHost;
145
- })) {
146
- return {
147
- authorized: false,
148
- errorMessage: "The domain is not authorized for this key.",
149
- errorCode: "DOMAIN_UNAUTHORIZED",
150
- statusCode: 403
151
- };
152
- }
153
+ const authResponse = authAccess(authOptions, keyData);
154
+ if (!authResponse?.authorized) {
155
+ return authResponse;
153
156
  }
154
-
155
- // validate services
156
- if (keyData.services && keyData.services?.length > 0) {
157
- const service = (keyData.services || []).find(srv => srv.name === scope);
158
- if (!service) {
159
- return {
160
- authorized: false,
161
- errorMessage: `The service "${scope}" is not authorized for this key.`,
162
- errorCode: "SERVICE_UNAUTHORIZED",
163
- statusCode: 403
164
- };
165
- }
166
-
167
- // validate service actions
168
- if (serviceActions) {
169
- let unknownAction;
170
- serviceActions.forEach(action => {
171
- if (!service.actions.includes(action)) {
172
- unknownAction = action;
173
- }
174
- });
175
- if (unknownAction) {
176
- return {
177
- authorized: false,
178
- errorMessage: `The service "${scope}" action "${unknownAction}" is not authorized for this key.`,
179
- errorCode: "SERVICE_ACTION_UNAUTHORIZED",
180
- statusCode: 403
181
- };
182
- }
183
- }
184
-
185
- // validate service target addresses
186
- if (serviceTargetAddresses && !service.targetAddresses.find(addr => addr === "*" || serviceTargetAddresses.includes(addr))) {
187
- return {
188
- authorized: false,
189
- errorMessage: `The service "${scope}" target address is not authorized for this key.`,
190
- errorCode: "SERVICE_TARGET_ADDRESS_UNAUTHORIZED",
191
- statusCode: 403
192
- };
193
- }
157
+ const authzResponse = authzServices(validations, keyData, scope);
158
+ if (!authzResponse?.authorized) {
159
+ return authzResponse;
194
160
  }
161
+ // FIXME: validate bundleId
195
162
 
196
- // validate bundleIds
197
- // if (
198
- // bundleIds &&
199
- // !keyData.bundleIds.find((addr) => addr === "*" || bundleIds.includes(addr))
200
- // ) {
201
- // return {
202
- // authorized: false,
203
- // errorMessage: `The service "${scope}" for BundlerIds ${bundleIds} is not authorized for this key.`,
204
- // errorCode: "SERVICE_TARGET_ADDRESS_UNAUTHORIZED",
205
- // statusCode: 403,
206
- // };
207
- // }
208
163
  return {
209
164
  authorized: true,
210
165
  data: keyData
211
166
  };
212
167
  } catch (err) {
213
- console.error("Failed to authorize this key", err);
168
+ console.error("Failed to authorize this key.", err);
214
169
  return {
215
170
  authorized: false,
216
171
  errorMessage: "Internal error",
@@ -219,63 +174,122 @@ async function authorize(clientId, authOpts, validations) {
219
174
  };
220
175
  }
221
176
  }
222
- async function authorizeNodeService(options) {
223
- if (!options.clientId) {
177
+ function authAccess(authOptions, apiKey) {
178
+ const {
179
+ origin,
180
+ secretHash: providedSecretHash
181
+ } = authOptions;
182
+ const {
183
+ domains,
184
+ secretHash
185
+ } = apiKey;
186
+ if (providedSecretHash) {
187
+ if (secretHash !== providedSecretHash) {
188
+ return {
189
+ authorized: false,
190
+ errorMessage: "The secret is invalid.",
191
+ errorCode: "SECRET_INVALID",
192
+ statusCode: 401
193
+ };
194
+ }
224
195
  return {
225
- authorized: false,
226
- errorMessage: "The API key is missing. Make sure it is included with your Authorization Bearer request header.",
227
- errorCode: "MISSING_API_KEY",
228
- statusCode: 422
196
+ authorized: true
229
197
  };
230
198
  }
231
- const origin = typeof options.headers["Origin"] === "string" ? options.headers["Origin"] : options.headers["Origin"]?.join("");
232
- let originHost;
199
+
200
+ // validate domains
233
201
  if (origin) {
234
- try {
235
- const originUrl = new URL(origin);
236
- originHost = originUrl.hostname;
237
- } catch (error) {
238
- // ignore, will be verified by domains
202
+ if (
203
+ // find matching domain, or if all domains allowed
204
+ domains.find(d => {
205
+ if (d === "*") {
206
+ return true;
207
+ }
208
+
209
+ // If the allowedDomain has a wildcard,
210
+ // we'll check that the ending of our domain matches the wildcard
211
+ if (d.startsWith("*.")) {
212
+ const domainRoot = d.slice(2);
213
+ return origin.endsWith(domainRoot);
214
+ }
215
+
216
+ // If there's no wildcard, we'll check for an exact match
217
+ return d === origin;
218
+ })) {
219
+ return {
220
+ authorized: true
221
+ };
239
222
  }
223
+ return {
224
+ authorized: false,
225
+ errorMessage: "The origin is not authorized for this key.",
226
+ errorCode: "ORIGIN_UNAUTHORIZED",
227
+ statusCode: 401
228
+ };
240
229
  }
241
- return authorize(options.clientId, {
242
- ...options.authOpts,
243
- origin: originHost,
244
- cachedKey: undefined
245
- }, options.validations);
230
+
231
+ // FIXME: validate bundle id
232
+ return {
233
+ authorized: false,
234
+ errorMessage: "The keys are invalid.",
235
+ errorCode: "UNAUTHORIZED",
236
+ statusCode: 401
237
+ };
238
+ }
239
+ function authzServices(validations, apiKey, scope) {
240
+ const {
241
+ services
242
+ } = apiKey;
243
+ const {
244
+ serviceTargetAddresses,
245
+ serviceAction
246
+ } = validations;
247
+
248
+ // validate services
249
+ const service = services.find(srv => srv.name === scope);
250
+ if (!service) {
251
+ return {
252
+ authorized: false,
253
+ errorMessage: `The service "${scope}" is not authorized for this key.`,
254
+ errorCode: "SERVICE_UNAUTHORIZED",
255
+ statusCode: 403
256
+ };
257
+ }
258
+
259
+ // validate service actions
260
+ if (serviceAction) {
261
+ if (!service.actions.includes(serviceAction)) {
262
+ return {
263
+ authorized: false,
264
+ errorMessage: `The service "${scope}" action "${serviceAction}" is not authorized for this key.`,
265
+ errorCode: "SERVICE_ACTION_UNAUTHORIZED",
266
+ statusCode: 403
267
+ };
268
+ }
269
+ }
270
+
271
+ // validate service target addresses
272
+ if (serviceTargetAddresses && !service.targetAddresses.find(addr => addr === "*" || serviceTargetAddresses.includes(addr))) {
273
+ return {
274
+ authorized: false,
275
+ errorMessage: `The service "${scope}" target address is not authorized for this key.`,
276
+ errorCode: "SERVICE_TARGET_ADDRESS_UNAUTHORIZED",
277
+ statusCode: 403
278
+ };
279
+ }
280
+ return {
281
+ authorized: true
282
+ };
246
283
  }
247
284
 
248
- const SERVICES = [{
249
- name: "storage",
250
- title: "Storage",
251
- description: "IPFS Upload and Download",
252
- actions: [{
253
- name: "read",
254
- title: "Download",
255
- description: "Download a file from Storage"
256
- }, {
257
- name: "write",
258
- title: "Upload",
259
- description: "Upload a file to Storage"
260
- }]
261
- }, {
262
- name: "rpc",
263
- title: "RPC",
264
- description: "Accelerated RPC Edge",
265
- // all actions allowed
266
- actions: []
267
- }, {
268
- name: "bundler",
269
- title: "Smart Wallets",
270
- description: "Bundler & Paymaster services",
271
- // all actions allowed
272
- actions: []
273
- }];
274
285
  function getServiceByName(name) {
275
286
  return SERVICES.find(srv => srv.name === name);
276
287
  }
277
288
 
278
289
  exports.SERVICES = SERVICES;
290
+ exports.SERVICE_NAMES = SERVICE_NAMES;
291
+ exports.authorizeCFWorkerService = authorizeCFWorkerService;
279
292
  exports.authorizeNodeService = authorizeNodeService;
280
- exports.authorizeWorkerService = authorizeWorkerService;
281
293
  exports.getServiceByName = getServiceByName;
294
+ exports.hashClientId = hashClientId;
295
+ exports.hashSecret = hashSecret;