From c61447f1986e1e0bf2b9fedf095d73184ee1dc08 Mon Sep 17 00:00:00 2001 From: Harminder Virk Date: Fri, 27 Oct 2023 14:58:37 +0530 Subject: [PATCH] feat: add browser client plugin --- package.json | 14 +++- src/auth/plugins/japa/api_client.ts | 100 ++++++++++++++---------- src/auth/plugins/japa/browser_client.ts | 90 +++++++++++++++++++++ 3 files changed, 160 insertions(+), 44 deletions(-) create mode 100644 src/auth/plugins/japa/browser_client.ts diff --git a/package.json b/package.json index fa1a593..2d2abd9 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,7 @@ "./types": "./build/src/auth/types.js", "./auth_provider": "./build/providers/auth_provider.js", "./plugins/api_client": "./build/src/auth/plugins/japa/api_client.js", + "./plugins/browser_client": "./build/src/auth/plugins/japa/browser_client.js", "./services/main": "./build/services/auth.js", "./core/token": "./build/src/core/token.js", "./core/guard_user": "./build/src/core/guard_user.js", @@ -78,8 +79,10 @@ "@commitlint/config-conventional": "^18.0.0", "@japa/api-client": "^2.0.0", "@japa/assert": "^2.0.0", + "@japa/browser-client": "^2.0.0", "@japa/expect-type": "^2.0.0", "@japa/file-system": "^2.0.0", + "@japa/plugin-adonisjs": "^2.0.0", "@japa/runner": "^3.0.4", "@japa/snapshot": "^2.0.0", "@swc/core": "1.3.82", @@ -96,6 +99,7 @@ "husky": "^8.0.3", "luxon": "^3.4.3", "np": "^8.0.4", + "playwright": "^1.39.0", "prettier": "^3.0.3", "set-cookie-parser": "^2.6.0", "sqlite3": "^5.1.6", @@ -140,7 +144,9 @@ "@adonisjs/core": "^6.1.5-31", "@adonisjs/lucid": "^19.0.0-3", "@adonisjs/session": "^7.0.0-13", - "@japa/api-client": "^2.0.0" + "@japa/api-client": "^2.0.0", + "@japa/browser-client": "^2.0.0", + "@japa/plugin-adonisjs": "^2.0.0" }, "peerDependenciesMeta": { "@adonisjs/lucid": { @@ -151,6 +157,12 @@ }, "@japa/api-client": { "optional": true + }, + "@japa/browser-client": { + "optional": true + }, + "@japa/plugin-adonisjs": { + "optional": true } } } diff --git a/src/auth/plugins/japa/api_client.ts b/src/auth/plugins/japa/api_client.ts index 01791ff..d99dd2f 100644 --- a/src/auth/plugins/japa/api_client.ts +++ b/src/auth/plugins/japa/api_client.ts @@ -9,8 +9,11 @@ /// +import type { PluginFn } from '@japa/runner/types' import { ApiClient, ApiRequest } from '@japa/api-client' import type { ApplicationService } from '@adonisjs/core/types' + +import debug from '../../debug.js' import type { Authenticators, GuardContract, GuardFactory } from '../../types.js' declare module '@japa/api-client' { @@ -58,51 +61,62 @@ declare module '@japa/api-client' { * HTTP requests using the Japa API client */ export const authApiClient = (app: ApplicationService) => { - ApiRequest.macro('loginAs', function (this: ApiRequest, user) { - this.authData = { - guard: '__default__', - user: user, - } - return this - }) + const pluginFn: PluginFn = function () { + debug('installing auth api client plugin') - ApiRequest.macro('withGuard', function < - K extends keyof Authenticators, - Self extends ApiRequest, - >(this: Self, guard: K) { - return { - loginAs: (user) => { - this.authData = { - guard, - user: user, - } - return this - }, - } - }) + ApiRequest.macro('loginAs', function (this: ApiRequest, user) { + this.authData = { + guard: '__default__', + user: user, + } + return this + }) - /** - * Hook into the request and login the user - */ - ApiClient.setup(async (request) => { - const auth = await app.container.make('auth.manager') - const authData = request['authData'] - if (!authData) { - return - } + ApiRequest.macro('withGuard', function < + K extends keyof Authenticators, + Self extends ApiRequest, + >(this: Self, guard: K) { + return { + loginAs: (user) => { + this.authData = { + guard, + user: user, + } + return this + }, + } + }) + + /** + * Hook into the request and login the user + */ + ApiClient.setup(async (request) => { + const auth = await app.container.make('auth.manager') + const authData = request['authData'] + if (!authData) { + return + } - const client = auth.createAuthenticatorClient() - const guard = authData.guard === '__default__' ? client.use() : client.use(authData.guard) - const requestData = await (guard as GuardContract).authenticateAsClient(authData.user) + const client = auth.createAuthenticatorClient() + const guard = authData.guard === '__default__' ? client.use() : client.use(authData.guard) + const requestData = await (guard as GuardContract).authenticateAsClient( + authData.user + ) - if (requestData.headers) { - request.headers(requestData.headers) - } - if (requestData.session) { - request.withSession(requestData.session) - } - if (requestData.cookies) { - request.cookies(requestData.cookies) - } - }) + if (requestData.headers) { + debug('defining headers with api client request %O', requestData.headers) + request.headers(requestData.headers) + } + if (requestData.session) { + debug('defining session with api client request %O', requestData.session) + request.withSession(requestData.session) + } + if (requestData.cookies) { + debug('defining session with api client request %O', requestData.session) + request.cookies(requestData.cookies) + } + }) + } + + return pluginFn } diff --git a/src/auth/plugins/japa/browser_client.ts b/src/auth/plugins/japa/browser_client.ts new file mode 100644 index 0000000..40591d7 --- /dev/null +++ b/src/auth/plugins/japa/browser_client.ts @@ -0,0 +1,90 @@ +/* + * @adoniss/auth + * + * (c) AdonisJS + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/// +/// + +import { RuntimeException } from '@poppinss/utils' +import type { PluginFn } from '@japa/runner/types' +import { decoratorsCollection } from '@japa/browser-client' +import type { ApplicationService } from '@adonisjs/core/types' + +import debug from '../../debug.js' +import type { Authenticators, GuardContract, GuardFactory } from '../../types.js' + +declare module 'playwright' { + export interface BrowserContext { + /** + * Login a user using the default authentication + * guard when using the browser context to + * make page visits + */ + loginAs(user: { + [K in keyof Authenticators]: Authenticators[K] extends GuardFactory + ? ReturnType extends GuardContract + ? A + : never + : never + }): Promise + + /** + * Define the authentication guard for login + */ + withGuard( + guard: K + ): { + /** + * Login a user using a specific auth guard + */ + loginAs( + user: Authenticators[K] extends GuardFactory + ? ReturnType extends GuardContract + ? A + : never + : never + ): Promise + } + } +} + +export const authBrowserClient = (app: ApplicationService) => { + const pluginFn: PluginFn = async function () { + debug('installing auth browser client plugin') + + const auth = await app.container.make('auth.manager') + + decoratorsCollection.register({ + context(context) { + context.loginAs = async function (user) { + const client = auth.createAuthenticatorClient() + const guard = client.use() as GuardContract + const requestData = await guard.authenticateAsClient(user) + + if (requestData.headers) { + throw new RuntimeException(`Cannot use "${guard.driverName}" guard with browser client`) + } + + if (requestData.cookies) { + debug('defining cookies with browser context %O', requestData.cookies) + Object.keys(requestData.cookies).forEach((cookie) => { + context.setCookie(cookie, requestData.cookies![cookie]) + }) + } + + if (requestData.session) { + debug('defining session with browser context %O', requestData.session) + context.setSession(requestData.session) + } + } + }, + }) + } + + return pluginFn +}