Skip to content
Merged
12 changes: 9 additions & 3 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
timeout-minutes: 60

services:
docker:
Expand All @@ -22,8 +23,13 @@ jobs:
- name: Set up Node.js
uses: actions/setup-node@v3
with:
node-version: '16' # Specify your Node.js version here
node-version: "18" # Specify your Node.js version here

- name: Set up pnpm
run: |
corepack enable
corepack prepare pnpm --activate
- name: Install dependencies
run: npm install
run: pnpm install
- name: Run integration tests
run: npm test # Make sure this command runs your integration tests
run: pnpm test # Make sure this command runs your integration tests
1 change: 0 additions & 1 deletion .npmrc
Original file line number Diff line number Diff line change
@@ -1 +0,0 @@
ignore-workspace-root-check=true
137 changes: 101 additions & 36 deletions __test__/index.test.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,17 @@
import { randomUUID } from 'node:crypto';
import { GenericContainer, StartedTestContainer, Wait } from 'testcontainers';
import { ApiResponse, AuthToken, Authorizer } from '../lib';

const authorizerConfig = {
jest.setTimeout(1200000); // Integration tests can be slow on CI (20 minutes)

const authorizerConfig: {
authorizerURL: string;
redirectURL: string;
adminSecret: string;
clientID?: string;
} = {
authorizerURL: 'http://localhost:8080',
redirectURL: 'http://localhost:8080/app',
// clientID: '3fab5e58-5693-46f2-8123-83db8133cd22',
adminSecret: 'secret',
};

Expand All @@ -18,38 +25,71 @@ const testConfig = {
maginLinkLoginEmail: 'test_magic_link@test.com',
};

// Using etheral.email for email sink: https://ethereal.email/create
const authorizerENV = {
ENV: 'production',
DATABASE_URL: 'data.db',
DATABASE_TYPE: 'sqlite',
CUSTOM_ACCESS_TOKEN_SCRIPT:
'function(user,tokenPayload){var data = tokenPayload;data.extra = {\'x-extra-id\': user.id};return data;}',
DISABLE_PLAYGROUND: 'true',
SMTP_HOST: 'smtp.ethereal.email',
SMTP_PASSWORD: 'WncNxwVFqb6nBjKDQJ',
SMTP_USERNAME: 'sydnee.lesch77@ethereal.email',
LOG_LELVEL: 'debug',
SMTP_PORT: '587',
SENDER_EMAIL: 'test@authorizer.dev',
ADMIN_SECRET: 'secret',
};
// Build v2 CLI args for authorizer (see authorizer/cmd/root.go). Using etheral.email for email sink.
function buildAuthorizerCliArgs(): { args: string[]; clientId: string } {
const clientId = randomUUID();
const clientSecret = randomUUID();
const jwtSecret = randomUUID();

const args = [
'--client-id',
clientId,
'--client-secret',
clientSecret,
'--jwt-type',
'HS256',
'--jwt-secret',
jwtSecret,
'--admin-secret',
authorizerConfig.adminSecret,
'--env',
'production',
'--database-type',
'sqlite',
'--database-url',
'/tmp/authorizer.db',
'--enable-playground=false',
'--log-level',
'debug',
'--smtp-host',
'smtp.ethereal.email',
'--smtp-port',
'587',
'--smtp-username',
'sydnee.lesch77@ethereal.email',
'--smtp-password',
'WncNxwVFqb6nBjKDQJ',
'--smtp-sender-email',
'test@authorizer.dev',
'--enable-email-verification=true',
'--enable-magic-link-login=true',
];
return { args, clientId };
}

// const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));

const verificationRequests =
'query {_verification_requests { verification_requests { id token email expires identifier } } }';

describe('Integration Tests - authorizer-js', () => {
let container: StartedTestContainer;
let container: StartedTestContainer | undefined;

let authorizer: Authorizer;

beforeAll(async () => {
container = await new GenericContainer('lakhansamani/authorizer:latest')
.withEnvironment(authorizerENV)
const { args, clientId } = buildAuthorizerCliArgs();

container = await new GenericContainer('lakhansamani/authorizer:2.0.0-rc.6')
.withCommand(args)
.withExposedPorts(8080)
.withWaitStrategy(Wait.forHttp('/health', 8080).forStatusCode(200))
.withStartupTimeout(900000) // 15 minutes (CI can be slow)
// Surface container stdout/stderr to help diagnose CI startup failures.
.withLogConsumer((chunk) => {
// Avoid changing log format; just mirror what container prints.
process.stdout.write(chunk.toString());
})
.start();

authorizerConfig.authorizerURL = `http://${container.getHost()}:${container.getMappedPort(
Expand All @@ -58,19 +98,13 @@ describe('Integration Tests - authorizer-js', () => {
authorizerConfig.redirectURL = `http://${container.getHost()}:${container.getMappedPort(
8080,
)}/app`;
authorizerConfig.clientID = clientId;
console.log('Authorizer URL:', authorizerConfig.authorizerURL);
authorizer = new Authorizer(authorizerConfig);
// get metadata
const metadataRes = await authorizer.getMetaData();
// await sleep(50000);
expect(metadataRes?.data).toBeDefined();
if (metadataRes?.data?.client_id) {
authorizer.config.clientID = metadataRes?.data?.client_id;
}
});

afterAll(async () => {
await container.stop();
if (container) await container.stop();
});

it('should signup with email verification enabled', async () => {
Expand Down Expand Up @@ -101,10 +135,13 @@ describe('Integration Tests - authorizer-js', () => {
(i: { email: string }) => i.email === testConfig.email,
);
expect(item).not.toBeNull();
expect(item?.token).toBeDefined();

const verifyEmailRes = await authorizer.verifyEmail({ token: item.token });
expect(verifyEmailRes?.data).toBeDefined();
expect(verifyEmailRes?.errors).toHaveLength(0);
expect(verifyEmailRes?.data?.access_token).toBeDefined();
expect(verifyEmailRes?.data?.access_token).not.toBeNull();
expect(verifyEmailRes?.data?.access_token?.length).toBeGreaterThan(0);
});

Expand All @@ -117,13 +154,23 @@ describe('Integration Tests - authorizer-js', () => {
});
expect(loginRes?.data).toBeDefined();
expect(loginRes?.errors).toHaveLength(0);
expect(loginRes?.data?.access_token.length).not.toEqual(0);
expect(loginRes?.data?.refresh_token?.length).not.toEqual(0);
expect(loginRes?.data?.expires_in).not.toEqual(0);
expect(loginRes?.data?.id_token.length).not.toEqual(0);
expect(loginRes?.data?.access_token).toBeDefined();
expect(loginRes?.data?.access_token).not.toBeNull();
expect(loginRes?.data?.access_token?.length).toBeGreaterThan(0);
expect(loginRes?.data?.refresh_token).toBeDefined();
expect(loginRes?.data?.refresh_token).not.toBeNull();
expect(loginRes?.data?.refresh_token?.length).toBeGreaterThan(0);
expect(loginRes?.data?.expires_in).toBeDefined();
expect(loginRes?.data?.expires_in).not.toBeNull();
expect(loginRes?.data?.expires_in).toBeGreaterThan(0);
expect(loginRes?.data?.id_token).toBeDefined();
expect(loginRes?.data?.id_token).not.toBeNull();
expect(loginRes?.data?.id_token?.length).toBeGreaterThan(0);
});

it('should validate jwt token', async () => {
expect(loginRes?.data?.access_token).toBeDefined();
expect(loginRes?.data?.access_token).not.toBeNull();
const validateRes = await authorizer.validateJWTToken({
token_type: 'access_token',
token: loginRes?.data?.access_token || '',
Expand All @@ -134,6 +181,8 @@ describe('Integration Tests - authorizer-js', () => {
});

it('should update profile successfully', async () => {
expect(loginRes?.data?.access_token).toBeDefined();
expect(loginRes?.data?.access_token).not.toBeNull();
const updateProfileRes = await authorizer.updateProfile(
{
given_name: 'bob',
Expand All @@ -147,28 +196,36 @@ describe('Integration Tests - authorizer-js', () => {
});

it('should fetch profile successfully', async () => {
expect(loginRes?.data?.access_token).toBeDefined();
expect(loginRes?.data?.access_token).not.toBeNull();
const profileRes = await authorizer.getProfile({
Authorization: `Bearer ${loginRes?.data?.access_token}`,
});
expect(profileRes?.data).toBeDefined();
expect(profileRes?.errors).toHaveLength(0);
expect(profileRes?.data?.given_name).toBeDefined();
expect(profileRes?.data?.given_name).toMatch('bob');
});

it('should get access_token using refresh_token', async () => {
expect(loginRes?.data?.refresh_token).toBeDefined();
expect(loginRes?.data?.refresh_token).not.toBeNull();
const tokenRes = await authorizer.getToken({
grant_type: 'refresh_token',
refresh_token: loginRes?.data?.refresh_token,
refresh_token: loginRes?.data?.refresh_token || '',
});
expect(tokenRes?.data).toBeDefined();
expect(tokenRes?.errors).toHaveLength(0);
expect(tokenRes?.data?.access_token.length).not.toEqual(0);
expect(tokenRes?.data?.access_token).toBeDefined();
expect(tokenRes?.data?.access_token?.length).toBeGreaterThan(0);
if (loginRes && loginRes.data) {
loginRes.data.access_token = tokenRes?.data?.access_token || '';
}
});

it('should deactivate account', async () => {
expect(loginRes?.data?.access_token).toBeDefined();
expect(loginRes?.data?.access_token).not.toBeNull();
const deactivateRes = await authorizer.deactivateAccount({
Authorization: `Bearer ${loginRes?.data?.access_token}`,
});
Expand All @@ -177,11 +234,13 @@ describe('Integration Tests - authorizer-js', () => {
});

it('should throw error while accessing profile after deactivation', async () => {
expect(loginRes?.data?.access_token).toBeDefined();
const resp = await authorizer.getProfile({
Authorization: `Bearer ${loginRes?.data?.access_token}`,
});
expect(resp?.data).toBeUndefined();
expect(resp?.errors).toHaveLength(1);
expect(resp?.errors).toBeDefined();
expect(resp?.errors.length).toBeGreaterThan(0);
});

describe('magic link login', () => {
Expand All @@ -195,22 +254,28 @@ describe('Integration Tests - authorizer-js', () => {
it('should verify email', async () => {
const verificationRequestsRes = await authorizer.graphqlQuery({
query: verificationRequests,
variables: {},
headers: {
'x-authorizer-admin-secret': authorizerConfig.adminSecret,
},
});
const requests =
verificationRequestsRes?.data?._verification_requests
.verification_requests;
expect(verificationRequestsRes?.data).toBeDefined();
expect(verificationRequestsRes?.errors).toHaveLength(0);
const item = requests.find(
(i: { email: string }) => i.email === testConfig.maginLinkLoginEmail,
);
expect(item).not.toBeNull();
expect(item?.token).toBeDefined();
const verifyEmailRes = await authorizer.verifyEmail({
token: item.token,
});
expect(verifyEmailRes?.data).toBeDefined();
expect(verifyEmailRes?.errors).toHaveLength(0);
expect(verifyEmailRes?.data?.user).toBeDefined();
expect(verifyEmailRes?.data?.user?.signup_methods).toBeDefined();
expect(verifyEmailRes?.data?.user?.signup_methods).toContain(
'magic_link_login',
);
Expand Down
12 changes: 3 additions & 9 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@authorizerdev/authorizer-js",
"version": "2.0.3",
"version": "3.0.0-rc.1",
"packageManager": "pnpm@7.28.0",
"author": "Lakhan Samani",
"license": "MIT",
Expand Down Expand Up @@ -30,14 +30,14 @@
"node": ">=16"
},
"scripts": {
"start": "rollup -w --config rollup.test.config.js",
"ts-types": "tsc --emitDeclarationOnly --outDir lib",
"start": "tsup --watch",
"build": "tsup",
"test": "npm run build && jest --testTimeout=500000 --runInBand",
"prepare": "husky install",
"prepublishOnly": "npm run build",
"release": "pnpm build && bumpp --commit --push --tag && pnpm publish",
"release-beta": "pnpm build && bumpp --commit --push --tag && pnpm publish --tag beta",
"release-rc": "pnpm build && pnpm publish --tag rc",
"lint": "eslint --ignore-pattern 'tsup.config.ts' --ext .ts,.tsx,.js,.jsx,.json .",
"lint:fix": "eslint --ignore-pattern 'tsup.config.ts' --ext .ts,.tsx,.js,.jsx,.json . --fix"
},
Expand All @@ -54,9 +54,6 @@
},
"devDependencies": {
"@antfu/eslint-config": "^2.1.0",
"@rollup/plugin-node-resolve": "^15.2.3",
"@rollup/plugin-terser": "^0.4.4",
"@rollup/plugin-typescript": "^11.1.5",
"@swc/core": "^1.3.99",
"@types/jest": "^29.5.12",
"@types/node": "^20.9.4",
Expand All @@ -67,9 +64,6 @@
"husky": "^8.0.0",
"jest": "^29.7.0",
"lint-staged": "^15.2.0",
"rollup": "^2.79.1",
"rollup-plugin-filesize": "^10.0.0",
"rollup-plugin-serve": "^2.0.2",
"testcontainers": "^10.3.2",
"ts-jest": "^29.1.1",
"tslib": "^2.6.2",
Expand Down
Loading
Loading