@addon-pass/sdk 0.1.3

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 (47) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +147 -0
  3. package/dist/contract.d.ts +9 -0
  4. package/dist/contract.d.ts.map +1 -0
  5. package/dist/contract.js +111 -0
  6. package/dist/contract.js.map +1 -0
  7. package/dist/errors.d.ts +22 -0
  8. package/dist/errors.d.ts.map +1 -0
  9. package/dist/errors.js +43 -0
  10. package/dist/errors.js.map +1 -0
  11. package/dist/express.d.ts +12 -0
  12. package/dist/express.d.ts.map +1 -0
  13. package/dist/express.js +64 -0
  14. package/dist/express.js.map +1 -0
  15. package/dist/fastify.d.ts +14 -0
  16. package/dist/fastify.d.ts.map +1 -0
  17. package/dist/fastify.js +63 -0
  18. package/dist/fastify.js.map +1 -0
  19. package/dist/fetch.d.ts +11 -0
  20. package/dist/fetch.d.ts.map +1 -0
  21. package/dist/fetch.js +48 -0
  22. package/dist/fetch.js.map +1 -0
  23. package/dist/index.d.ts +9 -0
  24. package/dist/index.d.ts.map +1 -0
  25. package/dist/index.js +9 -0
  26. package/dist/index.js.map +1 -0
  27. package/dist/node.d.ts +4 -0
  28. package/dist/node.d.ts.map +1 -0
  29. package/dist/node.js +59 -0
  30. package/dist/node.js.map +1 -0
  31. package/dist/stremio.d.ts +28 -0
  32. package/dist/stremio.d.ts.map +1 -0
  33. package/dist/stremio.js +226 -0
  34. package/dist/stremio.js.map +1 -0
  35. package/dist/token.d.ts +4 -0
  36. package/dist/token.d.ts.map +1 -0
  37. package/dist/token.js +24 -0
  38. package/dist/token.js.map +1 -0
  39. package/dist/types.d.ts +32 -0
  40. package/dist/types.d.ts.map +1 -0
  41. package/dist/types.js +53 -0
  42. package/dist/types.js.map +1 -0
  43. package/dist/verifier.d.ts +20 -0
  44. package/dist/verifier.d.ts.map +1 -0
  45. package/dist/verifier.js +255 -0
  46. package/dist/verifier.js.map +1 -0
  47. package/package.json +72 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Timothy Z.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,147 @@
1
+ # AddonPass middleware SDK
2
+
3
+ `@addon-pass/sdk` protects private Stremio JSON routes in the same process as the add-on handlers.
4
+
5
+ The SDK is currently a Base Sepolia `0.x` release. Install the reviewed public
6
+ package directly:
7
+
8
+ ```sh
9
+ pnpm add @addon-pass/sdk@0.1.3
10
+ ```
11
+
12
+ The public SDK repository contains only middleware source. The AddonPass
13
+ application, deployment configuration, credentials, and Sepolia contract source
14
+ remain private.
15
+
16
+ ## Request boundary
17
+
18
+ For a request such as:
19
+
20
+ ```text
21
+ /addonpass/{token}/stream/movie/addonpass:reference-film.json
22
+ ```
23
+
24
+ the middleware:
25
+
26
+ 1. accepts only a canonical 43-character, unpadded base64url token containing 32 bytes;
27
+ 2. hashes the decoded bytes locally with Ethereum `keccak256`;
28
+ 3. sends only the hash to `POST /v1/entitlements/verify` with the add-on-scoped integration credential;
29
+ 4. checks the returned plan against the configured allowlist;
30
+ 5. removes the bearer-token prefix before the add-on handler runs; and
31
+ 6. serves the add-on response only for active or grace access.
32
+
33
+ The raw token is never sent to AddonPass. Do not log request URLs, attach third-party analytics to protected routes, or expose the same handlers on another path, port, or hostname.
34
+
35
+ ## Supported adapters
36
+
37
+ - Framework-neutral Fetch handler: `createFetchStremioHandler`
38
+ - Native Node HTTP handler: `createNodeStremioHandler`
39
+ - Express 5 middleware: `@addon-pass/sdk/express`
40
+ - Fastify 5 pre-handler: `@addon-pass/sdk/fastify`
41
+
42
+ The framework packages are optional peers. Import only the adapter used by the add-on.
43
+
44
+ ## Native Node example
45
+
46
+ ```ts
47
+ import { createServer } from "node:http";
48
+
49
+ import {
50
+ AddonPassVerifier,
51
+ createNodeStremioHandler,
52
+ } from "@addon-pass/sdk";
53
+
54
+ const verifier = new AddonPassVerifier({
55
+ allowedPlanIds: ["PLAN_ID"],
56
+ apiBaseUrl: "https://api.addonpass.example",
57
+ integrationCredential: "ADDON_SCOPED_CREDENTIAL",
58
+ });
59
+
60
+ The AddonPass integration test subscribes with a hidden test plan that reports no plan id, so it passes regardless of `allowedPlanIds`.
61
+
62
+ const handle = createNodeStremioHandler({
63
+ access: {
64
+ addonId: "com.example.private-addon",
65
+ addonName: "Private Add-on",
66
+ managementUrl: "https://addonpass.example/subscriptions",
67
+ },
68
+ verifier,
69
+ upstream: async (request, authorization) => {
70
+ // request.url now contains /manifest.json or the normal Stremio resource
71
+ // path. authorization contains the verified decision and parsed route, but
72
+ // never the raw bearer token.
73
+ return Response.json({
74
+ resource: authorization.route.resource,
75
+ path: new URL(request.url).pathname,
76
+ });
77
+ },
78
+ });
79
+
80
+ createServer((request, response) => {
81
+ void handle(request, response);
82
+ }).listen(3002, "127.0.0.1");
83
+ ```
84
+
85
+ Use placeholders in source control. Load the API URL, plan IDs, management URL, and integration credential from the add-on process environment.
86
+
87
+ ## Express and Fastify
88
+
89
+ Mount the Express middleware globally before every add-on handler. The middleware rejects `/manifest.json` without a token and rewrites an entitled request before Express performs downstream routing.
90
+
91
+ ```ts
92
+ import express from "express";
93
+ import {
94
+ createExpressStremioMiddleware,
95
+ expressAddonPassAuthorization,
96
+ } from "@addon-pass/sdk/express";
97
+
98
+ const app = express();
99
+ app.use(createExpressStremioMiddleware({ access, verifier }));
100
+ app.get("/manifest.json", (_request, response) => {
101
+ const authorization = expressAddonPassAuthorization(response);
102
+ response.json(buildManifest(authorization));
103
+ });
104
+ ```
105
+
106
+ For Fastify, attach the same protection instance to every protected route and read the request-scoped result from it.
107
+
108
+ ```ts
109
+ import { createFastifyStremioProtection } from "@addon-pass/sdk/fastify";
110
+
111
+ const protection = createFastifyStremioProtection({ access, verifier });
112
+ app.get(
113
+ "/addonpass/:token/manifest.json",
114
+ { preHandler: protection.preHandler },
115
+ (request) => buildManifest(protection.authorization(request)),
116
+ );
117
+ ```
118
+
119
+ Do not register an unprotected copy of either handler.
120
+
121
+ ## Verification behavior
122
+
123
+ | State | Result |
124
+ | --- | --- |
125
+ | Active or grace | Invoke the add-on handler and return its Stremio JSON. |
126
+ | Expired, cancelled, or authorization ended | Return valid Stremio JSON with the configured management URL; do not invoke the add-on. |
127
+ | Unknown or malformed token | Return a generic `404` without disclosing entitlement state. |
128
+ | API network error or `5xx` | Use the optional safe-block contract fallback when configured; otherwise return `503`. |
129
+ | Rejected credential, scope mismatch, or invalid API response | Fail closed. These errors never activate contract fallback. |
130
+
131
+ Successful API decisions use a bounded in-memory LRU cache. Its TTL is at most five seconds and is shortened so it can never extend access beyond paid-through or grace.
132
+
133
+ ## Direct contract fallback
134
+
135
+ `createViemEntitlementFallback` reads the configured non-upgradeable contract at one safe block and cross-checks the stored subscription, hash, developer, chain, and plan scope. It is opt-in. Configure a bounded RPC transport and never treat a credential rejection as an API outage.
136
+
137
+ Use the same verifier instance for every protected route and keep its integration
138
+ credential in the add-on server environment.
139
+
140
+ ## Local verification
141
+
142
+ ```sh
143
+ npm install
144
+ npm run check
145
+ ```
146
+
147
+ The suites cover active, grace, terminal, malformed-token, unavailable-provider, cache, fallback, CORS, route-scrubbing, and unprotected-route denial behavior using controlled entitlement decisions. They do not replace the required deployed positive-entitlement integration proof before checkout publication.
@@ -0,0 +1,9 @@
1
+ import { type Address, type PublicClient } from "viem";
2
+ import type { EntitlementFallback } from "./types.js";
3
+ export declare function createViemEntitlementFallback(input: {
4
+ readonly chainId: number;
5
+ readonly client: Pick<PublicClient, "getBlock" | "getChainId" | "readContract">;
6
+ readonly contractAddress: Address;
7
+ readonly expectedDeveloperAddress: Address;
8
+ }): EntitlementFallback;
9
+ //# sourceMappingURL=contract.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"contract.d.ts","sourceRoot":"","sources":["../src/contract.ts"],"names":[],"mappings":"AAAA,OAAO,EAKL,KAAK,OAAO,EACZ,KAAK,YAAY,EAClB,MAAM,MAAM,CAAC;AAOd,OAAO,KAAK,EAAE,mBAAmB,EAAuB,MAAM,YAAY,CAAC;AA6B3E,wBAAgB,6BAA6B,CAAC,KAAK,EAAE;IACnD,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,MAAM,EAAE,IAAI,CACnB,YAAY,EACZ,UAAU,GAAG,YAAY,GAAG,cAAc,CAC3C,CAAC;IACF,QAAQ,CAAC,eAAe,EAAE,OAAO,CAAC;IAClC,QAAQ,CAAC,wBAAwB,EAAE,OAAO,CAAC;CAC5C,GAAG,mBAAmB,CA6FtB"}
@@ -0,0 +1,111 @@
1
+ import { getAddress, isAddressEqual, parseAbi, zeroAddress, } from "viem";
2
+ import { AddonPassConfigurationError, EntitlementScopeMismatchError, EntitlementVerificationError, } from "./errors.js";
3
+ const ENTITLEMENT_ABI = parseAbi([
4
+ "function entitlementStatus(bytes32 entitlementHash) view returns (bool entitled, uint64 paidThrough, uint64 graceEnds, uint256 subscriptionId, address developer)",
5
+ "function subscriptions(uint256 subscriptionId) view returns (uint256 planId, address payer, bytes32 entitlementHash, uint64 paidThrough, uint32 remainingCharges, bool cancelled)",
6
+ ]);
7
+ function isoFromSeconds(value) {
8
+ const milliseconds = value * 1000n;
9
+ if (milliseconds > BigInt(Number.MAX_SAFE_INTEGER)) {
10
+ throw new EntitlementVerificationError();
11
+ }
12
+ return new Date(Number(milliseconds)).toISOString();
13
+ }
14
+ function notFound() {
15
+ return {
16
+ entitled: false,
17
+ finality: null,
18
+ graceEnds: null,
19
+ paidThrough: null,
20
+ planId: null,
21
+ sourceBlock: null,
22
+ sourceBlockHash: null,
23
+ status: "not_found",
24
+ subscriptionId: null,
25
+ };
26
+ }
27
+ export function createViemEntitlementFallback(input) {
28
+ if (!Number.isSafeInteger(input.chainId) || input.chainId <= 0) {
29
+ throw new AddonPassConfigurationError();
30
+ }
31
+ let contractAddress;
32
+ let expectedDeveloperAddress;
33
+ try {
34
+ contractAddress = getAddress(input.contractAddress);
35
+ expectedDeveloperAddress = getAddress(input.expectedDeveloperAddress);
36
+ }
37
+ catch {
38
+ throw new AddonPassConfigurationError();
39
+ }
40
+ if (isAddressEqual(expectedDeveloperAddress, zeroAddress)) {
41
+ throw new AddonPassConfigurationError();
42
+ }
43
+ return {
44
+ async verify(tokenHash) {
45
+ try {
46
+ const [actualChainId, block] = await Promise.all([
47
+ input.client.getChainId(),
48
+ input.client.getBlock({ blockTag: "safe" }),
49
+ ]);
50
+ if (actualChainId !== input.chainId) {
51
+ throw new EntitlementVerificationError();
52
+ }
53
+ const [entitled, paidThrough, graceEnds, subscriptionId, developer] = await input.client.readContract({
54
+ abi: ENTITLEMENT_ABI,
55
+ address: contractAddress,
56
+ args: [tokenHash],
57
+ blockNumber: block.number,
58
+ functionName: "entitlementStatus",
59
+ });
60
+ if (subscriptionId === 0n)
61
+ return notFound();
62
+ if (!isAddressEqual(developer, expectedDeveloperAddress)) {
63
+ throw new EntitlementScopeMismatchError();
64
+ }
65
+ const [planId, , storedHash, storedPaidThrough, remainingCharges, cancelled,] = await input.client.readContract({
66
+ abi: ENTITLEMENT_ABI,
67
+ address: contractAddress,
68
+ args: [subscriptionId],
69
+ blockNumber: block.number,
70
+ functionName: "subscriptions",
71
+ });
72
+ if (storedHash !== tokenHash || storedPaidThrough !== paidThrough) {
73
+ throw new EntitlementVerificationError();
74
+ }
75
+ const active = block.timestamp <= paidThrough;
76
+ if (active !== entitled &&
77
+ !(entitled && block.timestamp <= graceEnds)) {
78
+ throw new EntitlementVerificationError();
79
+ }
80
+ const status = active
81
+ ? "active"
82
+ : entitled
83
+ ? "grace"
84
+ : cancelled
85
+ ? "cancelled"
86
+ : remainingCharges === 0
87
+ ? "authorization_ended"
88
+ : "expired";
89
+ return {
90
+ entitled,
91
+ finality: "safe",
92
+ graceEnds: isoFromSeconds(graceEnds),
93
+ paidThrough: isoFromSeconds(paidThrough),
94
+ planId: planId.toString(10),
95
+ sourceBlock: block.number.toString(10),
96
+ sourceBlockHash: block.hash,
97
+ status,
98
+ subscriptionId: subscriptionId.toString(10),
99
+ };
100
+ }
101
+ catch (error) {
102
+ if (error instanceof EntitlementScopeMismatchError ||
103
+ error instanceof EntitlementVerificationError) {
104
+ throw error;
105
+ }
106
+ throw new EntitlementVerificationError();
107
+ }
108
+ },
109
+ };
110
+ }
111
+ //# sourceMappingURL=contract.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"contract.js","sourceRoot":"","sources":["../src/contract.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,22 @@
1
+ export declare class AddonPassConfigurationError extends Error {
2
+ constructor();
3
+ }
4
+ export declare class AddonPassUnavailableError extends Error {
5
+ constructor();
6
+ }
7
+ export declare class EntitlementCredentialRejectedError extends Error {
8
+ constructor();
9
+ }
10
+ export declare class EntitlementScopeMismatchError extends Error {
11
+ constructor();
12
+ }
13
+ export declare class EntitlementVerificationError extends Error {
14
+ constructor();
15
+ }
16
+ export declare class InvalidEntitlementTokenError extends Error {
17
+ constructor();
18
+ }
19
+ export declare class UnsupportedStremioRouteError extends Error {
20
+ constructor();
21
+ }
22
+ //# sourceMappingURL=errors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,qBAAa,2BAA4B,SAAQ,KAAK;;CAKrD;AAED,qBAAa,yBAA0B,SAAQ,KAAK;;CAKnD;AAED,qBAAa,kCAAmC,SAAQ,KAAK;;CAK5D;AAED,qBAAa,6BAA8B,SAAQ,KAAK;;CAKvD;AAED,qBAAa,4BAA6B,SAAQ,KAAK;;CAKtD;AAED,qBAAa,4BAA6B,SAAQ,KAAK;;CAKtD;AAED,qBAAa,4BAA6B,SAAQ,KAAK;;CAKtD"}
package/dist/errors.js ADDED
@@ -0,0 +1,43 @@
1
+ export class AddonPassConfigurationError extends Error {
2
+ constructor() {
3
+ super("AddonPass verifier configuration is invalid");
4
+ this.name = "AddonPassConfigurationError";
5
+ }
6
+ }
7
+ export class AddonPassUnavailableError extends Error {
8
+ constructor() {
9
+ super("AddonPass entitlement verification is unavailable");
10
+ this.name = "AddonPassUnavailableError";
11
+ }
12
+ }
13
+ export class EntitlementCredentialRejectedError extends Error {
14
+ constructor() {
15
+ super("AddonPass integration credential was rejected");
16
+ this.name = "EntitlementCredentialRejectedError";
17
+ }
18
+ }
19
+ export class EntitlementScopeMismatchError extends Error {
20
+ constructor() {
21
+ super("Entitlement does not match the configured add-on scope");
22
+ this.name = "EntitlementScopeMismatchError";
23
+ }
24
+ }
25
+ export class EntitlementVerificationError extends Error {
26
+ constructor() {
27
+ super("AddonPass entitlement verification failed");
28
+ this.name = "EntitlementVerificationError";
29
+ }
30
+ }
31
+ export class InvalidEntitlementTokenError extends Error {
32
+ constructor() {
33
+ super("Entitlement token is invalid");
34
+ this.name = "InvalidEntitlementTokenError";
35
+ }
36
+ }
37
+ export class UnsupportedStremioRouteError extends Error {
38
+ constructor() {
39
+ super("Stremio route is not protected by this handler");
40
+ this.name = "UnsupportedStremioRouteError";
41
+ }
42
+ }
43
+ //# sourceMappingURL=errors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,MAAM,OAAO,2BAA4B,SAAQ,KAAK;IACpD;QACE,KAAK,CAAC,6CAA6C,CAAC,CAAC;QACrD,IAAI,CAAC,IAAI,GAAG,6BAA6B,CAAC;IAC5C,CAAC;CACF;AAED,MAAM,OAAO,yBAA0B,SAAQ,KAAK;IAClD;QACE,KAAK,CAAC,mDAAmD,CAAC,CAAC;QAC3D,IAAI,CAAC,IAAI,GAAG,2BAA2B,CAAC;IAC1C,CAAC;CACF;AAED,MAAM,OAAO,kCAAmC,SAAQ,KAAK;IAC3D;QACE,KAAK,CAAC,+CAA+C,CAAC,CAAC;QACvD,IAAI,CAAC,IAAI,GAAG,oCAAoC,CAAC;IACnD,CAAC;CACF;AAED,MAAM,OAAO,6BAA8B,SAAQ,KAAK;IACtD;QACE,KAAK,CAAC,wDAAwD,CAAC,CAAC;QAChE,IAAI,CAAC,IAAI,GAAG,+BAA+B,CAAC;IAC9C,CAAC;CACF;AAED,MAAM,OAAO,4BAA6B,SAAQ,KAAK;IACrD;QACE,KAAK,CAAC,2CAA2C,CAAC,CAAC;QACnD,IAAI,CAAC,IAAI,GAAG,8BAA8B,CAAC;IAC7C,CAAC;CACF;AAED,MAAM,OAAO,4BAA6B,SAAQ,KAAK;IACrD;QACE,KAAK,CAAC,8BAA8B,CAAC,CAAC;QACtC,IAAI,CAAC,IAAI,GAAG,8BAA8B,CAAC;IAC7C,CAAC;CACF;AAED,MAAM,OAAO,4BAA6B,SAAQ,KAAK;IACrD;QACE,KAAK,CAAC,gDAAgD,CAAC,CAAC;QACxD,IAAI,CAAC,IAAI,GAAG,8BAA8B,CAAC;IAC7C,CAAC;CACF"}
@@ -0,0 +1,12 @@
1
+ import type { RequestHandler, Response as ExpressResponse } from "express";
2
+ import { type AuthorizedStremioRequest, type StremioAccessConfiguration } from "./stremio.js";
3
+ import type { AddonPassVerifier } from "./verifier.js";
4
+ export declare const EXPRESS_ADDONPASS_LOCAL = "addonPass";
5
+ export interface ExpressStremioMiddlewareOptions {
6
+ readonly access: StremioAccessConfiguration;
7
+ readonly mountPath?: string;
8
+ readonly verifier: Pick<AddonPassVerifier, "verifyToken">;
9
+ }
10
+ export declare function createExpressStremioMiddleware(options: ExpressStremioMiddlewareOptions): RequestHandler;
11
+ export declare function expressAddonPassAuthorization(response: ExpressResponse): AuthorizedStremioRequest;
12
+ //# sourceMappingURL=express.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"express.d.ts","sourceRoot":"","sources":["../src/express.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,QAAQ,IAAI,eAAe,EAAE,MAAM,SAAS,CAAC;AAU3E,OAAO,EAGL,KAAK,wBAAwB,EAC7B,KAAK,0BAA0B,EAChC,MAAM,cAAc,CAAC;AACtB,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAEvD,eAAO,MAAM,uBAAuB,cAAc,CAAC;AAEnD,MAAM,WAAW,+BAA+B;IAC9C,QAAQ,CAAC,MAAM,EAAE,0BAA0B,CAAC;IAC5C,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,QAAQ,EAAE,IAAI,CAAC,iBAAiB,EAAE,aAAa,CAAC,CAAC;CAC3D;AAkBD,wBAAgB,8BAA8B,CAC5C,OAAO,EAAE,+BAA+B,GACvC,cAAc,CAyDhB;AAED,wBAAgB,6BAA6B,CAC3C,QAAQ,EAAE,eAAe,GACxB,wBAAwB,CAK1B"}
@@ -0,0 +1,64 @@
1
+ import { AddonPassUnavailableError, EntitlementCredentialRejectedError, EntitlementScopeMismatchError, EntitlementVerificationError, InvalidEntitlementTokenError, UnsupportedStremioRouteError, } from "./errors.js";
2
+ import { authorizeStremioRequest, createStremioAccessResponder, } from "./stremio.js";
3
+ export const EXPRESS_ADDONPASS_LOCAL = "addonPass";
4
+ async function send(target, response, head) {
5
+ target.status(response.status);
6
+ response.headers.forEach((value, name) => {
7
+ target.setHeader(name, value);
8
+ });
9
+ if (head || response.body === null) {
10
+ target.end();
11
+ return;
12
+ }
13
+ target.send(Buffer.from(await response.arrayBuffer()));
14
+ }
15
+ export function createExpressStremioMiddleware(options) {
16
+ const responder = createStremioAccessResponder(options.access);
17
+ const mountPath = options.mountPath ?? "/addonpass";
18
+ return async (request, response, next) => {
19
+ if (request.method === "OPTIONS") {
20
+ await send(response, responder.options(), false);
21
+ return;
22
+ }
23
+ if (request.method !== "GET" && request.method !== "HEAD") {
24
+ await send(response, responder.methodNotAllowed(), false);
25
+ return;
26
+ }
27
+ try {
28
+ const url = new URL(request.originalUrl, "http://addonpass.local");
29
+ const authorization = await authorizeStremioRequest(options.verifier, url, mountPath);
30
+ if (!authorization.decision.entitled) {
31
+ await send(response, responder.denied(authorization), request.method === "HEAD");
32
+ return;
33
+ }
34
+ const safeUrl = `${authorization.route.upstreamPath}${url.search}`;
35
+ request.url = safeUrl;
36
+ request.originalUrl = safeUrl;
37
+ response.locals[EXPRESS_ADDONPASS_LOCAL] = authorization;
38
+ response.setHeader("access-control-allow-origin", "*");
39
+ next();
40
+ }
41
+ catch (error) {
42
+ if (error instanceof UnsupportedStremioRouteError ||
43
+ error instanceof InvalidEntitlementTokenError) {
44
+ await send(response, responder.invalid(), request.method === "HEAD");
45
+ return;
46
+ }
47
+ if (error instanceof AddonPassUnavailableError ||
48
+ error instanceof EntitlementCredentialRejectedError ||
49
+ error instanceof EntitlementScopeMismatchError ||
50
+ error instanceof EntitlementVerificationError) {
51
+ await send(response, responder.unavailable(), request.method === "HEAD");
52
+ return;
53
+ }
54
+ next(error);
55
+ }
56
+ };
57
+ }
58
+ export function expressAddonPassAuthorization(response) {
59
+ const authorization = response.locals[EXPRESS_ADDONPASS_LOCAL];
60
+ if (authorization === undefined)
61
+ throw new EntitlementVerificationError();
62
+ return authorization;
63
+ }
64
+ //# sourceMappingURL=express.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"express.js","sourceRoot":"","sources":["../src/express.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,14 @@
1
+ import type { FastifyRequest, preHandlerAsyncHookHandler } from "fastify";
2
+ import { type AuthorizedStremioRequest, type StremioAccessConfiguration } from "./stremio.js";
3
+ import type { AddonPassVerifier } from "./verifier.js";
4
+ export interface FastifyStremioProtection {
5
+ authorization(request: FastifyRequest): AuthorizedStremioRequest;
6
+ readonly preHandler: preHandlerAsyncHookHandler;
7
+ }
8
+ export interface FastifyStremioProtectionOptions {
9
+ readonly access: StremioAccessConfiguration;
10
+ readonly mountPath?: string;
11
+ readonly verifier: Pick<AddonPassVerifier, "verifyToken">;
12
+ }
13
+ export declare function createFastifyStremioProtection(options: FastifyStremioProtectionOptions): FastifyStremioProtection;
14
+ //# sourceMappingURL=fastify.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fastify.d.ts","sourceRoot":"","sources":["../src/fastify.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAEV,cAAc,EACd,0BAA0B,EAC3B,MAAM,SAAS,CAAC;AAUjB,OAAO,EAGL,KAAK,wBAAwB,EAC7B,KAAK,0BAA0B,EAChC,MAAM,cAAc,CAAC;AACtB,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAEvD,MAAM,WAAW,wBAAwB;IACvC,aAAa,CAAC,OAAO,EAAE,cAAc,GAAG,wBAAwB,CAAC;IACjE,QAAQ,CAAC,UAAU,EAAE,0BAA0B,CAAC;CACjD;AAED,MAAM,WAAW,+BAA+B;IAC9C,QAAQ,CAAC,MAAM,EAAE,0BAA0B,CAAC;IAC5C,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,QAAQ,EAAE,IAAI,CAAC,iBAAiB,EAAE,aAAa,CAAC,CAAC;CAC3D;AAcD,wBAAgB,8BAA8B,CAC5C,OAAO,EAAE,+BAA+B,GACvC,wBAAwB,CAyD1B"}
@@ -0,0 +1,63 @@
1
+ import { AddonPassUnavailableError, EntitlementCredentialRejectedError, EntitlementScopeMismatchError, EntitlementVerificationError, InvalidEntitlementTokenError, UnsupportedStremioRouteError, } from "./errors.js";
2
+ import { authorizeStremioRequest, createStremioAccessResponder, } from "./stremio.js";
3
+ async function send(reply, response) {
4
+ reply.code(response.status);
5
+ response.headers.forEach((value, name) => {
6
+ void reply.header(name, value);
7
+ });
8
+ if (response.body === null || reply.request.method === "HEAD") {
9
+ await reply.send();
10
+ return;
11
+ }
12
+ await reply.send(Buffer.from(await response.arrayBuffer()));
13
+ }
14
+ export function createFastifyStremioProtection(options) {
15
+ const authorizations = new WeakMap();
16
+ const mountPath = options.mountPath ?? "/addonpass";
17
+ const responder = createStremioAccessResponder(options.access);
18
+ return {
19
+ authorization(request) {
20
+ const authorization = authorizations.get(request);
21
+ if (authorization === undefined)
22
+ throw new EntitlementVerificationError();
23
+ return authorization;
24
+ },
25
+ async preHandler(request, reply) {
26
+ if (request.method === "OPTIONS") {
27
+ await send(reply, responder.options());
28
+ return;
29
+ }
30
+ if (request.method !== "GET" && request.method !== "HEAD") {
31
+ await send(reply, responder.methodNotAllowed());
32
+ return;
33
+ }
34
+ try {
35
+ const url = new URL(request.raw.url ?? "/", "http://addonpass.local");
36
+ const authorization = await authorizeStremioRequest(options.verifier, url, mountPath);
37
+ if (!authorization.decision.entitled) {
38
+ await send(reply, responder.denied(authorization));
39
+ return;
40
+ }
41
+ authorizations.set(request, authorization);
42
+ request.raw.url = `${authorization.route.upstreamPath}${url.search}`;
43
+ void reply.header("access-control-allow-origin", "*");
44
+ }
45
+ catch (error) {
46
+ if (error instanceof UnsupportedStremioRouteError ||
47
+ error instanceof InvalidEntitlementTokenError) {
48
+ await send(reply, responder.invalid());
49
+ return;
50
+ }
51
+ if (error instanceof AddonPassUnavailableError ||
52
+ error instanceof EntitlementCredentialRejectedError ||
53
+ error instanceof EntitlementScopeMismatchError ||
54
+ error instanceof EntitlementVerificationError) {
55
+ await send(reply, responder.unavailable());
56
+ return;
57
+ }
58
+ throw error;
59
+ }
60
+ },
61
+ };
62
+ }
63
+ //# sourceMappingURL=fastify.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fastify.js","sourceRoot":"","sources":["../src/fastify.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ import { type AuthorizedStremioRequest, type StremioAccessConfiguration } from "./stremio.js";
2
+ import type { AddonPassVerifier } from "./verifier.js";
3
+ export type FetchStremioUpstream = (request: Request, authorization: AuthorizedStremioRequest) => Promise<Response> | Response;
4
+ export interface FetchStremioHandlerOptions {
5
+ readonly access: StremioAccessConfiguration;
6
+ readonly mountPath?: string;
7
+ readonly upstream: FetchStremioUpstream;
8
+ readonly verifier: Pick<AddonPassVerifier, "verifyToken">;
9
+ }
10
+ export declare function createFetchStremioHandler(options: FetchStremioHandlerOptions): (request: Request) => Promise<Response>;
11
+ //# sourceMappingURL=fetch.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fetch.d.ts","sourceRoot":"","sources":["../src/fetch.ts"],"names":[],"mappings":"AAQA,OAAO,EAGL,KAAK,wBAAwB,EAC7B,KAAK,0BAA0B,EAEhC,MAAM,cAAc,CAAC;AACtB,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAEvD,MAAM,MAAM,oBAAoB,GAAG,CACjC,OAAO,EAAE,OAAO,EAChB,aAAa,EAAE,wBAAwB,KACpC,OAAO,CAAC,QAAQ,CAAC,GAAG,QAAQ,CAAC;AAElC,MAAM,WAAW,0BAA0B;IACzC,QAAQ,CAAC,MAAM,EAAE,0BAA0B,CAAC;IAC5C,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,QAAQ,EAAE,oBAAoB,CAAC;IACxC,QAAQ,CAAC,QAAQ,EAAE,IAAI,CAAC,iBAAiB,EAAE,aAAa,CAAC,CAAC;CAC3D;AAYD,wBAAgB,yBAAyB,CACvC,OAAO,EAAE,0BAA0B,GAClC,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,QAAQ,CAAC,CAkDzC"}
package/dist/fetch.js ADDED
@@ -0,0 +1,48 @@
1
+ import { AddonPassUnavailableError, EntitlementCredentialRejectedError, EntitlementScopeMismatchError, EntitlementVerificationError, InvalidEntitlementTokenError, UnsupportedStremioRouteError, } from "./errors.js";
2
+ import { authorizeStremioRequest, createStremioAccessResponder, } from "./stremio.js";
3
+ function withCors(response, head) {
4
+ const headers = new Headers(response.headers);
5
+ headers.set("access-control-allow-origin", "*");
6
+ return new Response(head ? null : response.body, {
7
+ headers,
8
+ status: response.status,
9
+ statusText: response.statusText,
10
+ });
11
+ }
12
+ export function createFetchStremioHandler(options) {
13
+ const responder = createStremioAccessResponder(options.access);
14
+ const mountPath = options.mountPath ?? "/addonpass";
15
+ return async (request) => {
16
+ if (request.method === "OPTIONS")
17
+ return responder.options();
18
+ if (request.method !== "GET" && request.method !== "HEAD") {
19
+ return responder.methodNotAllowed();
20
+ }
21
+ let route;
22
+ try {
23
+ const url = new URL(request.url);
24
+ const authorization = await authorizeStremioRequest(options.verifier, url, mountPath);
25
+ route = authorization.route;
26
+ if (!authorization.decision.entitled) {
27
+ return withCors(responder.denied(authorization), request.method === "HEAD");
28
+ }
29
+ url.pathname = authorization.route.upstreamPath;
30
+ const upstreamRequest = new Request(url, request);
31
+ return withCors(await options.upstream(upstreamRequest, authorization), request.method === "HEAD");
32
+ }
33
+ catch (error) {
34
+ if (error instanceof UnsupportedStremioRouteError ||
35
+ error instanceof InvalidEntitlementTokenError) {
36
+ return withCors(responder.invalid(), request.method === "HEAD");
37
+ }
38
+ if (error instanceof AddonPassUnavailableError ||
39
+ error instanceof EntitlementCredentialRejectedError ||
40
+ error instanceof EntitlementScopeMismatchError ||
41
+ error instanceof EntitlementVerificationError) {
42
+ return withCors(responder.unavailable(route), request.method === "HEAD");
43
+ }
44
+ throw error;
45
+ }
46
+ };
47
+ }
48
+ //# sourceMappingURL=fetch.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fetch.js","sourceRoot":"","sources":["../src/fetch.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,yBAAyB,EACzB,kCAAkC,EAClC,6BAA6B,EAC7B,4BAA4B,EAC5B,4BAA4B,EAC5B,4BAA4B,GAC7B,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,uBAAuB,EACvB,4BAA4B,GAI7B,MAAM,cAAc,CAAC;AAetB,SAAS,QAAQ,CAAC,QAAkB,EAAE,IAAa;IACjD,MAAM,OAAO,GAAG,IAAI,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;IAC9C,OAAO,CAAC,GAAG,CAAC,6BAA6B,EAAE,GAAG,CAAC,CAAC;IAChD,OAAO,IAAI,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,EAAE;QAC/C,OAAO;QACP,MAAM,EAAE,QAAQ,CAAC,MAAM;QACvB,UAAU,EAAE,QAAQ,CAAC,UAAU;KAChC,CAAC,CAAC;AACL,CAAC;AAED,MAAM,UAAU,yBAAyB,CACvC,OAAmC;IAEnC,MAAM,SAAS,GAAG,4BAA4B,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;IAC/D,MAAM,SAAS,GAAG,OAAO,CAAC,SAAS,IAAI,YAAY,CAAC;IACpD,OAAO,KAAK,EAAE,OAAO,EAAE,EAAE;QACvB,IAAI,OAAO,CAAC,MAAM,KAAK,SAAS;YAAE,OAAO,SAAS,CAAC,OAAO,EAAE,CAAC;QAC7D,IAAI,OAAO,CAAC,MAAM,KAAK,KAAK,IAAI,OAAO,CAAC,MAAM,KAAK,MAAM,EAAE,CAAC;YAC1D,OAAO,SAAS,CAAC,gBAAgB,EAAE,CAAC;QACtC,CAAC;QACD,IAAI,KAA+B,CAAC;QACpC,IAAI,CAAC;YACH,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;YACjC,MAAM,aAAa,GAAG,MAAM,uBAAuB,CACjD,OAAO,CAAC,QAAQ,EAChB,GAAG,EACH,SAAS,CACV,CAAC;YACF,KAAK,GAAG,aAAa,CAAC,KAAK,CAAC;YAC5B,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,QAAQ,EAAE,CAAC;gBACrC,OAAO,QAAQ,CACb,SAAS,CAAC,MAAM,CAAC,aAAa,CAAC,EAC/B,OAAO,CAAC,MAAM,KAAK,MAAM,CAC1B,CAAC;YACJ,CAAC;YACD,GAAG,CAAC,QAAQ,GAAG,aAAa,CAAC,KAAK,CAAC,YAAY,CAAC;YAChD,MAAM,eAAe,GAAG,IAAI,OAAO,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;YAClD,OAAO,QAAQ,CACb,MAAM,OAAO,CAAC,QAAQ,CAAC,eAAe,EAAE,aAAa,CAAC,EACtD,OAAO,CAAC,MAAM,KAAK,MAAM,CAC1B,CAAC;QACJ,CAAC;QAAC,OAAO,KAAc,EAAE,CAAC;YACxB,IACE,KAAK,YAAY,4BAA4B;gBAC7C,KAAK,YAAY,4BAA4B,EAC7C,CAAC;gBACD,OAAO,QAAQ,CAAC,SAAS,CAAC,OAAO,EAAE,EAAE,OAAO,CAAC,MAAM,KAAK,MAAM,CAAC,CAAC;YAClE,CAAC;YACD,IACE,KAAK,YAAY,yBAAyB;gBAC1C,KAAK,YAAY,kCAAkC;gBACnD,KAAK,YAAY,6BAA6B;gBAC9C,KAAK,YAAY,4BAA4B,EAC7C,CAAC;gBACD,OAAO,QAAQ,CACb,SAAS,CAAC,WAAW,CAAC,KAAK,CAAC,EAC5B,OAAO,CAAC,MAAM,KAAK,MAAM,CAC1B,CAAC;YACJ,CAAC;YACD,MAAM,KAAK,CAAC;QACd,CAAC;IACH,CAAC,CAAC;AACJ,CAAC"}
@@ -0,0 +1,9 @@
1
+ export * from "./contract.js";
2
+ export * from "./errors.js";
3
+ export * from "./fetch.js";
4
+ export * from "./node.js";
5
+ export * from "./stremio.js";
6
+ export * from "./token.js";
7
+ export * from "./types.js";
8
+ export * from "./verifier.js";
9
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,eAAe,CAAC;AAC9B,cAAc,aAAa,CAAC;AAC5B,cAAc,YAAY,CAAC;AAC3B,cAAc,WAAW,CAAC;AAC1B,cAAc,cAAc,CAAC;AAC7B,cAAc,YAAY,CAAC;AAC3B,cAAc,YAAY,CAAC;AAC3B,cAAc,eAAe,CAAC"}