Merge pull request #35 from saleor/env-var-apl

Introduce new APLs: fileAPL and vercelAPL
This commit is contained in:
Krzysztof Wolski 2022-09-05 11:46:48 +02:00 committed by GitHub
commit cbaa2335b4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 549 additions and 9 deletions

View file

@ -9,7 +9,7 @@
"plugin:@typescript-eslint/recommended",
"prettier" // prettier *has* to be the last one, to avoid conflicting rules
],
"ignorePatterns": ["pnpm-lock.yaml"],
"ignorePatterns": ["pnpm-lock.yaml", "dist"],
"plugins": ["simple-import-sort", "@typescript-eslint"],
"rules": {
"import/no-extraneous-dependencies": ["error", { "devDependencies": true }],

View file

@ -3,3 +3,4 @@ saleor/api.tsx
pnpm-lock.yaml
graphql.schema.json
lib/$path.ts
dist

View file

@ -14,7 +14,7 @@ APL is an interface for managing auth data of registered Apps. Implementing it d
## Example implementation
Let's create an APL, which uses redis for data storage:
Let's create an APL, which uses Redis for data storage:
```ts
import { createClient } from "redis";
@ -66,3 +66,74 @@ const handler = async (request) => {
// the middleware will reject request if it's domain has not been registered
export default withRegisteredSaleorDomainHeader({ apl: redisAPL })(handler);
```
### Using different APL depending on the environment
Depending on the environment your app is working on, you may want to use a different APL. For example during local development you might like to use `FileAPL`, because it does not require any additional infrastructure. Deployed apps on the other hand need more robust solution.
To handle both scenarios, initialize the proper APLs in your code based on it's environment. In your application code:
```ts
// lib/saleorApp.ts
import { FileAPL, VercelAPL } from "@saleor/app-sdk/APL";
// Based on environment variable the app will use a different APL:
// - For local development store auth data in the `.auth-data.json`.
// - For app deployment on Vercel with Saleor CLI, use vercelAPL.
export const apl = process.env.VERCEL === "1" ? new VercelAPL() : new FileAPL();
```
Now you can use it for in your view:
```ts
import { SALEOR_DOMAIN_HEADER } from "@saleor/app-sdk/const";
import { withRegisteredSaleorDomainHeader } from "@saleor/app-sdk/middleware";
import type { Handler } from "retes";
import { toNextHandler } from "retes/adapter";
import { Response } from "retes/response";
// import created APL
import { apl } from "@lib/saleorApp";
const handler: Handler = async (request) => {
const saleorDomain = request.headers[SALEOR_DOMAIN_HEADER];
// Get auth data
const authData = apl.get(saleorDomain);
// view logic...
return Response.OK();
};
export default toNextHandler([withRegisteredSaleorDomainHeader({ apl }), handler]);
```
## Available APLs
### FileAPL
File based storage of auth data, intended for local development. Data are stored in the `.saleor-app-auth.json` file. You'll be able to develop app without additional dependencies or infrastructure.
Please note: this APL supports single tenant only (new registrations overwrite previous ones) and should not be used on production.
### VercelAPL
Single tenant APL dedicated for apps deployed on Vercel. Apps deployed from Marketplace and CLI automatically set up Vercel project for this APL (`SALEOR_REGISTER_APP_URL` and `SALEOR_DEPLOYMENT_TOKEN` variables).
The auth data are stored using environment variables:
```mermaid
sequenceDiagram
participant SI as Saleor Instance
participant A as App
participant SSI as Saleor x Vercel integration
participant V as Vercel
SI->>+A: Register
A->>SSI: Update auth data
A->>-SI: Register completed
SSI->>V: Set auth data as environment variables
V->>A: Redeploy the application
```

View file

@ -50,3 +50,4 @@ Use the namespace name to enable debug logs for each module.
| \app-sdk:\* | Enable all |
| app-sdk:AppBridge | Enable [AppBridge](./app-bridge.md) (browser only) |
| app-sdk:Middleware:\* | Enable all middlewares (node only) |
| app-sdk:APL:\* | Enable all APLs (node only) |

View file

@ -22,11 +22,13 @@
"fast-glob": "^3.2.11",
"graphql": "^16.5.0",
"jose": "^4.8.3",
"node-fetch": "^3.2.10",
"retes": "^0.32.0",
"uuid": "^8.3.2"
},
"devDependencies": {
"release-it": "^15.4.1",
"@types/node-fetch": "^2.6.2",
"@testing-library/dom": "^8.17.1",
"@types/debug": "^4.1.7",
"@types/node": "^18.6.5",

View file

@ -4,6 +4,7 @@ specifiers:
'@testing-library/dom': ^8.17.1
'@types/debug': ^4.1.7
'@types/node': ^18.6.5
'@types/node-fetch': ^2.6.2
'@types/uuid': ^8.3.4
'@typescript-eslint/eslint-plugin': ^5.33.0
'@typescript-eslint/parser': ^5.33.0
@ -24,6 +25,7 @@ specifiers:
husky: ^8.0.1
jose: ^4.8.3
jsdom: ^20.0.0
node-fetch: ^3.2.10
prettier: 2.7.1
release-it: ^15.4.1
retes: ^0.32.0
@ -40,6 +42,7 @@ dependencies:
fast-glob: 3.2.11
graphql: 16.5.0
jose: 4.8.3
node-fetch: 3.2.10
retes: 0.32.0
uuid: 8.3.2
@ -47,6 +50,7 @@ devDependencies:
'@testing-library/dom': 8.17.1
'@types/debug': 4.1.7
'@types/node': 18.7.1
'@types/node-fetch': 2.6.2
'@types/uuid': 8.3.4
'@typescript-eslint/eslint-plugin': 5.33.0_njno5y7ry2l2lcmiu4tywxkwnq
'@typescript-eslint/parser': 5.33.0_qugx7qdu5zevzvxaiqyxfiwquq
@ -686,6 +690,13 @@ packages:
resolution: {integrity: sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA==}
dev: true
/@types/node-fetch/2.6.2:
resolution: {integrity: sha512-DHqhlq5jeESLy19TYhLakJ07kNumXWjcDdxXsLUMJZ6ue8VZJj4kLPQVE/2mdHh3xZziNF1xppu5lwmS53HR+A==}
dependencies:
'@types/node': 18.7.1
form-data: 3.0.1
dev: true
/@types/node/18.7.1:
resolution: {integrity: sha512-GKX1Qnqxo4S+Z/+Z8KKPLpH282LD7jLHWJcVryOflnsnH+BtSDfieR6ObwBMwpnNws0bUK8GI7z0unQf9bARNQ==}
dev: true
@ -1446,7 +1457,6 @@ packages:
/data-uri-to-buffer/4.0.0:
resolution: {integrity: sha512-Vr3mLBA8qWmcuschSLAOogKgQ/Jwxulv3RNE4FXnYWRGujzrRWQI4m12fQqRkwX06C0KanhLr4hK+GydchZsaA==}
engines: {node: '>= 12'}
dev: true
/data-urls/3.0.2:
resolution: {integrity: sha512-Jy/tj3ldjZJo63sVAvg6LHt2mHvl4V6AgRAmNDtLdm7faqtsx+aJG42rsyCo9JCoRVKwPFzKlIPx3DIibwSIaQ==}
@ -2358,7 +2368,6 @@ packages:
dependencies:
node-domexception: 1.0.0
web-streams-polyfill: 3.2.1
dev: true
/figures/4.0.1:
resolution: {integrity: sha512-rElJwkA/xS04Vfg+CaZodpso7VqBknOYbzi6I76hI4X80RUjkSxO2oAyPmGbuXUppywjqndOrQDl817hDnI++w==}
@ -2418,6 +2427,15 @@ packages:
engines: {node: '>= 14.17'}
dev: true
/form-data/3.0.1:
resolution: {integrity: sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==}
engines: {node: '>= 6'}
dependencies:
asynckit: 0.4.0
combined-stream: 1.0.8
mime-types: 2.1.35
dev: true
/form-data/4.0.0:
resolution: {integrity: sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==}
engines: {node: '>= 6'}
@ -2432,7 +2450,6 @@ packages:
engines: {node: '>=12.20.0'}
dependencies:
fetch-blob: 3.2.0
dev: true
/fs-extra/8.1.0:
resolution: {integrity: sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==}
@ -3494,7 +3511,6 @@ packages:
/node-domexception/1.0.0:
resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==}
engines: {node: '>=10.5.0'}
dev: true
/node-fetch/2.6.7:
resolution: {integrity: sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==}
@ -3515,7 +3531,6 @@ packages:
data-uri-to-buffer: 4.0.0
fetch-blob: 3.2.0
formdata-polyfill: 4.0.10
dev: true
/node-releases/2.0.6:
resolution: {integrity: sha512-PiVXnNuFm5+iYkLBNeq5211hvO38y63T0i2KKh2KnUs3RpzJ+JtODFjkD8yjLwnDkTYF1eKXheUwdssR+NRZdg==}
@ -4899,7 +4914,6 @@ packages:
/web-streams-polyfill/3.2.1:
resolution: {integrity: sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q==}
engines: {node: '>= 8'}
dev: true
/webidl-conversions/3.0.1:
resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==}

3
src/APL/apl-debug.ts Normal file
View file

@ -0,0 +1,3 @@
import { createDebug } from "../debug";
export const createAPLDebug = (namespace: string) => createDebug(`APL:${namespace}`);

119
src/APL/file-apl.test.ts Normal file
View file

@ -0,0 +1,119 @@
import { promises as fsPromises } from "fs";
import { afterEach, describe, expect, it, vi } from "vitest";
import { FileAPL } from "./file-apl";
const stubAuthData = {
domain: "example.com",
token: "example-token",
};
describe("APL", () => {
afterEach(() => {
vi.clearAllMocks();
});
describe("FileAPL", () => {
describe("get", () => {
it("Should throw error when JSON parse fails", async () => {
vi.spyOn(fsPromises, "access").mockResolvedValue();
vi.spyOn(fsPromises, "readFile").mockResolvedValue("Not a valid JSON");
const apl = new FileAPL();
await expect(apl.get(stubAuthData.domain)).rejects.toThrow(
"File APL could not read auth data from the .saleor-app-auth.json file"
);
});
it("Returns auth data for existing domain", async () => {
vi.spyOn(fsPromises, "access").mockResolvedValue();
vi.spyOn(fsPromises, "readFile").mockResolvedValue(JSON.stringify(stubAuthData));
const apl = new FileAPL();
expect(await apl.get(stubAuthData.domain)).toStrictEqual(stubAuthData);
});
it("Returns undefined for unknown domain", async () => {
vi.spyOn(fsPromises, "access").mockResolvedValue();
vi.spyOn(fsPromises, "readFile").mockResolvedValue(JSON.stringify(stubAuthData));
const apl = new FileAPL();
expect(await apl.get("unknown-domain.example.com")).toBe(undefined);
});
});
describe("set", () => {
it("Handle write file errors", async () => {
const spyWriteFile = vi.spyOn(fsPromises, "writeFile").mockImplementation(() => {
throw Error("Write error");
});
const apl = new FileAPL();
await expect(apl.set(stubAuthData)).rejects.toThrow(
"File APL was unable to save auth data"
);
expect(spyWriteFile).toBeCalledWith(".saleor-app-auth.json", JSON.stringify(stubAuthData));
});
});
it("Successfully save to file", async () => {
const spyWriteFile = vi.spyOn(fsPromises, "writeFile").mockResolvedValue();
const apl = new FileAPL();
await expect(apl.set(stubAuthData));
expect(spyWriteFile).toBeCalledWith(".saleor-app-auth.json", JSON.stringify(stubAuthData));
});
describe("delete", () => {
it("Should override file when called with known domain", async () => {
vi.spyOn(fsPromises, "access").mockResolvedValue();
vi.spyOn(fsPromises, "readFile").mockResolvedValue(JSON.stringify(stubAuthData));
const spyWriteFile = vi.spyOn(fsPromises, "writeFile").mockResolvedValue();
const apl = new FileAPL();
await apl.delete(stubAuthData.domain);
expect(spyWriteFile).toBeCalledWith(".saleor-app-auth.json", "{}");
});
it("Should not delete data when called with unknown domain", async () => {
vi.spyOn(fsPromises, "access").mockResolvedValue();
vi.spyOn(fsPromises, "readFile").mockResolvedValue(JSON.stringify(stubAuthData));
const spyWriteFile = vi.spyOn(fsPromises, "writeFile").mockResolvedValue();
const apl = new FileAPL();
await apl.delete("unknown-domain.example.com");
expect(spyWriteFile).toBeCalledTimes(0);
});
});
describe("getAll", () => {
it("Should return list with one item when auth data are existing", async () => {
vi.spyOn(fsPromises, "access").mockResolvedValue();
vi.spyOn(fsPromises, "readFile").mockResolvedValue(JSON.stringify(stubAuthData));
const apl = new FileAPL();
expect(await apl.getAll()).toStrictEqual([stubAuthData]);
});
it("Should return empty list when auth data are empty", async () => {
vi.spyOn(fsPromises, "access").mockResolvedValue();
vi.spyOn(fsPromises, "readFile").mockResolvedValue("{}");
const apl = new FileAPL();
expect(await apl.getAll()).toStrictEqual([]);
});
});
});
});

101
src/APL/file-apl.ts Normal file
View file

@ -0,0 +1,101 @@
import { promises as fsPromises } from "fs";
import { APL, AuthData } from "./apl";
import { createAPLDebug } from "./apl-debug";
const debug = createAPLDebug("FileAPL");
export type FileAPLConfig = {
fileName?: string;
};
/**
* File APL
*
* The APL store auth data in the json file.
*
* Before using this APL, please take in consideration:
* - only stores single auth data entry (setting up a new one will overwrite previous values)
* - it's not recommended for production use - redeployment of the application will override
* existing values, or data persistence will not be guaranteed at all depending on chosen
* hosting solution
*
*/
export class FileAPL implements APL {
private fileName: string;
constructor(config: FileAPLConfig = {}) {
this.fileName = config?.fileName || ".saleor-app-auth.json";
}
/**
* Load auth data from a file and return it as AuthData format.
* In case of incomplete or invalid data, return `undefined`.
*
* @param {string} fileName
*/
private async loadDataFromFile(): Promise<AuthData | undefined> {
debug(`Load auth data from the ${this.fileName} file`);
let parsedData: Record<string, string> = {};
try {
await fsPromises.access(this.fileName);
parsedData = JSON.parse(await fsPromises.readFile(this.fileName, "utf-8"));
} catch (err) {
debug(`Could not read auth data from the ${this.fileName} file`, err);
throw new Error(`File APL could not read auth data from the ${this.fileName} file`);
}
const { token, domain } = parsedData;
if (token && domain) {
return { token, domain };
}
return undefined;
}
/**
* Save auth data to file.
* When `authData` argument is empty, will overwrite file with empty values.
*
* @param {string} fileName
* @param {AuthData} [authData]
*/
private async saveDataToFile(authData?: AuthData) {
debug(`Save auth data to the ${this.fileName} file`);
const newData = authData ? JSON.stringify(authData) : "{}";
try {
await fsPromises.writeFile(this.fileName, newData);
} catch (err) {
debug(`Could not save auth data to the ${this.fileName} file`, err);
throw new Error("File APL was unable to save auth data");
}
}
async get(domain: string) {
const authData = await this.loadDataFromFile();
if (domain === authData?.domain) {
return authData;
}
return undefined;
}
async set(authData: AuthData) {
await this.saveDataToFile(authData);
}
async delete(domain: string) {
const authData = await this.loadDataFromFile();
if (domain === authData?.domain) {
await this.saveDataToFile();
}
}
async getAll() {
const authData = await this.loadDataFromFile();
if (!authData) {
return [];
}
return [authData];
}
}

View file

@ -1 +1,3 @@
export * from "./apl";
export * from "./file-apl";
export * from "./vercel-apl";

112
src/APL/vercel-apl.test.ts Normal file
View file

@ -0,0 +1,112 @@
import { afterEach, describe, expect, it } from "vitest";
import { VercelAPL, VercelAPLVariables } from "./vercel-apl";
const aplConfig = {
deploymentToken: "token",
registerAppURL: "http://example.com",
};
const stubAuthData = {
domain: "example.com",
token: "example-token",
};
describe("APL", () => {
const initialEnv = { ...process.env };
afterEach(() => {
process.env = { ...initialEnv };
});
describe("VercelAPL", () => {
describe("constructor", () => {
it("Raise an error when configuration is missing", async () => {
delete process.env[VercelAPLVariables.SALEOR_REGISTER_APP_URL];
process.env[VercelAPLVariables.SALEOR_DEPLOYMENT_TOKEN] = "token";
expect(() => new VercelAPL()).toThrow();
process.env[VercelAPLVariables.SALEOR_REGISTER_APP_URL] = "http://example.com";
delete process.env[VercelAPLVariables.SALEOR_DEPLOYMENT_TOKEN];
expect(() => new VercelAPL()).toThrow();
});
});
it("Constructs VercelAPL instance when deploymentToken and registerAppURL provided", async () => {
expect(() => new VercelAPL(aplConfig)).not.toThrow();
});
it("Constructs VercelAPL instance with config values from environment variables", async () => {
process.env[VercelAPLVariables.SALEOR_REGISTER_APP_URL] = aplConfig.registerAppURL;
process.env[VercelAPLVariables.SALEOR_DEPLOYMENT_TOKEN] = aplConfig.deploymentToken;
expect(() => new VercelAPL()).not.toThrow();
});
it("Test if constructor use options over environment variables", async () => {
process.env[VercelAPLVariables.SALEOR_REGISTER_APP_URL] = "environment";
process.env[VercelAPLVariables.SALEOR_DEPLOYMENT_TOKEN] = "environment";
const apl = await new VercelAPL({ deploymentToken: "option", registerAppURL: "option" });
// eslint-disable-next-line dot-notation
expect(apl["deploymentToken"]).toBe("option");
// eslint-disable-next-line dot-notation
expect(apl["registerAppURL"]).toBe("option");
});
describe("get", () => {
describe("Read existing auth data from env", () => {
it("Read existing auth data", async () => {
process.env[VercelAPLVariables.TOKEN_VARIABLE_NAME] = stubAuthData.token;
process.env[VercelAPLVariables.DOMAIN_VARIABLE_NAME] = stubAuthData.domain;
const apl = new VercelAPL(aplConfig);
expect(await apl.get(stubAuthData.domain)).toStrictEqual(stubAuthData);
});
it("Return undefined when unknown domain requested", async () => {
process.env[VercelAPLVariables.TOKEN_VARIABLE_NAME] = stubAuthData.token;
process.env[VercelAPLVariables.DOMAIN_VARIABLE_NAME] = stubAuthData.domain;
const apl = new VercelAPL(aplConfig);
expect(await apl.get("unknown-domain.example.com")).toBe(undefined);
});
it("Return undefined when no data is defined", async () => {
delete process.env[VercelAPLVariables.TOKEN_VARIABLE_NAME];
delete process.env[VercelAPLVariables.DOMAIN_VARIABLE_NAME];
const apl = new VercelAPL(aplConfig);
expect(await apl.get("example.com")).toBe(undefined);
});
});
});
describe("getAll", () => {
describe("Read existing auth data from env", () => {
it("Read existing auth data", async () => {
process.env[VercelAPLVariables.TOKEN_VARIABLE_NAME] = stubAuthData.token;
process.env[VercelAPLVariables.DOMAIN_VARIABLE_NAME] = stubAuthData.domain;
const apl = new VercelAPL(aplConfig);
expect(await apl.getAll()).toStrictEqual([stubAuthData]);
});
it("Return empty list when no auth data are existing", async () => {
delete process.env[VercelAPLVariables.TOKEN_VARIABLE_NAME];
delete process.env[VercelAPLVariables.DOMAIN_VARIABLE_NAME];
const apl = new VercelAPL(aplConfig);
expect(await apl.getAll()).toStrictEqual([]);
});
});
});
});
});

114
src/APL/vercel-apl.ts Normal file
View file

@ -0,0 +1,114 @@
/* eslint-disable class-methods-use-this */
import fetch from "node-fetch";
import { APL, AuthData } from "./apl";
import { createAPLDebug } from "./apl-debug";
const debug = createAPLDebug("VercelAPL");
export const VercelAPLVariables = {
TOKEN_VARIABLE_NAME: "SALEOR_AUTH_TOKEN",
DOMAIN_VARIABLE_NAME: "SALEOR_DOMAIN",
SALEOR_REGISTER_APP_URL: "SALEOR_REGISTER_APP_URL",
SALEOR_DEPLOYMENT_TOKEN: "SALEOR_DEPLOYMENT_TOKEN",
};
const getEnvAuth = (): AuthData | undefined => {
const token = process.env[VercelAPLVariables.TOKEN_VARIABLE_NAME];
const domain = process.env[VercelAPLVariables.DOMAIN_VARIABLE_NAME];
if (!token || !domain) {
return undefined;
}
return {
token,
domain,
};
};
export type VercelAPLConfig = {
registerAppURL?: string;
deploymentToken?: string;
};
/** Vercel APL
*
* Use environment variables for auth data storage. To update data on existing deployment,
* theres Saleor microservice which update new values with the Vercel API and restarts the instance.
*
* This APL should be used for single tenant purposes due to it's limitations:
* - only stores single auth data entry (setting up a new one will overwrite previous values)
* - changing the environment variables require server restart
*
* With this APL we recommend using the [Saleor CLI](https://docs.saleor.io/docs/3.x/cli),
* which automatically set up the required environment variables during deployment:
* - SALEOR_REGISTER_APP_URL: the URL for microservice which set up variables using Vercel API
* - SALEOR_DEPLOYMENT_TOKEN: token for your particular Vercel deployment
*/
export class VercelAPL implements APL {
private registerAppURL: string;
private deploymentToken: string;
constructor(config?: VercelAPLConfig) {
const registerAppURL =
config?.registerAppURL || process.env[VercelAPLVariables.SALEOR_REGISTER_APP_URL];
if (!registerAppURL) {
throw new Error("Misconfiguration: please provide registerAppUrl");
}
const deploymentToken =
config?.deploymentToken || process.env[VercelAPLVariables.SALEOR_DEPLOYMENT_TOKEN];
if (!deploymentToken) {
throw new Error("Misconfiguration: please provide deploymentToken");
}
this.registerAppURL = registerAppURL;
this.deploymentToken = deploymentToken;
}
private async saveDataToVercel(authData?: AuthData) {
debug(`saveDataToVercel with: ${authData}`);
try {
await fetch(this.registerAppURL, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
token: this.deploymentToken,
envs: {
[VercelAPLVariables.TOKEN_VARIABLE_NAME]: authData?.token || "",
[VercelAPLVariables.DOMAIN_VARIABLE_NAME]: authData?.domain || "",
},
}),
});
} catch (error) {
debug("Error during saving the data:", error);
throw new Error(`VercelAPL was not able to save auth data${error}`);
}
}
async get(domain: string) {
const authData = getEnvAuth();
if (authData && domain === authData?.domain) {
return authData;
}
return undefined;
}
async set(authData: AuthData) {
await this.saveDataToVercel(authData);
}
async delete(domain: string) {
if (domain === getEnvAuth()?.domain) {
// Override existing data with the empty values
await this.saveDataToVercel();
}
}
async getAll() {
const authData = getEnvAuth();
if (!authData) {
return [];
}
return [authData];
}
}

View file

@ -5,7 +5,7 @@ import { createDebug } from "../debug";
export const createMiddlewareDebug = (middleware: string) =>
createDebug(`Middleware:${middleware}`);
type DebugFactory = (handlerName: string) => (msg: string, ...args: any[]) => void;
type DebugFactory = (handlerName: string) => (msg: string, ...args: unknown[]) => void;
/**
* Experimental. Needs to be tested and evaluated on security