@adonisjs/auth 9.0.0-5 → 9.0.0-7
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.
- package/build/factories/basic_auth_guard_factory.d.ts +12 -0
- package/build/factories/basic_auth_guard_factory.js +22 -0
- package/build/src/auth/auth_manager.d.ts +5 -0
- package/build/src/auth/auth_manager.js +7 -0
- package/build/src/auth/authenticator_client.d.ts +23 -0
- package/build/src/auth/authenticator_client.js +59 -0
- package/build/src/auth/errors.d.ts +5 -0
- package/build/src/auth/errors.js +17 -0
- package/build/src/auth/plugins/japa/api_client.d.ts +32 -0
- package/build/src/auth/plugins/japa/api_client.js +63 -0
- package/build/src/auth/plugins/japa/browser_client.d.ts +25 -0
- package/build/src/auth/plugins/japa/browser_client.js +42 -0
- package/build/src/auth/types.d.ts +14 -0
- package/build/src/guards/basic_auth/define_config.d.ts +16 -0
- package/build/src/guards/basic_auth/define_config.js +38 -0
- package/build/src/guards/basic_auth/guard.d.ts +70 -0
- package/build/src/guards/basic_auth/guard.js +190 -0
- package/build/src/guards/basic_auth/main.d.ts +2 -0
- package/build/src/guards/basic_auth/main.js +10 -0
- package/build/src/guards/basic_auth/types.d.ts +35 -0
- package/build/src/guards/basic_auth/types.js +9 -0
- package/build/src/guards/session/guard.d.ts +7 -0
- package/build/src/guards/session/guard.js +16 -0
- package/package.json +25 -4
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { HttpContext } from '@adonisjs/core/http';
|
|
2
|
+
import { FactoryUser, TestLucidUserProvider } from './lucid_user_provider.js';
|
|
3
|
+
import { BasicAuthGuard } from '../src/guards/basic_auth/guard.js';
|
|
4
|
+
import type { UserProviderContract } from '../src/core/types.js';
|
|
5
|
+
/**
|
|
6
|
+
* Exposes the API to create a basic auth guard for testing. Under
|
|
7
|
+
* the hood configures Lucid models for looking up users
|
|
8
|
+
*/
|
|
9
|
+
export declare class BasicAuthGuardFactory {
|
|
10
|
+
merge(): this;
|
|
11
|
+
create<UserProvider extends UserProviderContract<unknown> = TestLucidUserProvider<typeof FactoryUser>>(ctx: HttpContext, provider?: UserProvider): BasicAuthGuard<TestLucidUserProvider<typeof FactoryUser> | UserProvider>;
|
|
12
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* @adonisjs/auth
|
|
3
|
+
*
|
|
4
|
+
* (c) AdonisJS
|
|
5
|
+
*
|
|
6
|
+
* For the full copyright and license information, please view the LICENSE
|
|
7
|
+
* file that was distributed with this source code.
|
|
8
|
+
*/
|
|
9
|
+
import { LucidUserProviderFactory, } from './lucid_user_provider.js';
|
|
10
|
+
import { BasicAuthGuard } from '../src/guards/basic_auth/guard.js';
|
|
11
|
+
/**
|
|
12
|
+
* Exposes the API to create a basic auth guard for testing. Under
|
|
13
|
+
* the hood configures Lucid models for looking up users
|
|
14
|
+
*/
|
|
15
|
+
export class BasicAuthGuardFactory {
|
|
16
|
+
merge() {
|
|
17
|
+
return this;
|
|
18
|
+
}
|
|
19
|
+
create(ctx, provider) {
|
|
20
|
+
return new BasicAuthGuard('basic', ctx, provider || new LucidUserProviderFactory().create());
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { HttpContext } from '@adonisjs/core/http';
|
|
2
2
|
import type { GuardFactory } from './types.js';
|
|
3
3
|
import { Authenticator } from './authenticator.js';
|
|
4
|
+
import { AuthenticatorClient } from './authenticator_client.js';
|
|
4
5
|
/**
|
|
5
6
|
* Auth manager exposes the API to register and manage authentication
|
|
6
7
|
* guards from the config
|
|
@@ -19,4 +20,8 @@ export declare class AuthManager<KnownGuards extends Record<string, GuardFactory
|
|
|
19
20
|
* Create an authenticator for a given HTTP request
|
|
20
21
|
*/
|
|
21
22
|
createAuthenticator(ctx: HttpContext): Authenticator<KnownGuards>;
|
|
23
|
+
/**
|
|
24
|
+
* Creates an instance of the authenticator client
|
|
25
|
+
*/
|
|
26
|
+
createAuthenticatorClient(): AuthenticatorClient<KnownGuards>;
|
|
22
27
|
}
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
* file that was distributed with this source code.
|
|
8
8
|
*/
|
|
9
9
|
import { Authenticator } from './authenticator.js';
|
|
10
|
+
import { AuthenticatorClient } from './authenticator_client.js';
|
|
10
11
|
/**
|
|
11
12
|
* Auth manager exposes the API to register and manage authentication
|
|
12
13
|
* guards from the config
|
|
@@ -31,4 +32,10 @@ export class AuthManager {
|
|
|
31
32
|
createAuthenticator(ctx) {
|
|
32
33
|
return new Authenticator(ctx, this.#config);
|
|
33
34
|
}
|
|
35
|
+
/**
|
|
36
|
+
* Creates an instance of the authenticator client
|
|
37
|
+
*/
|
|
38
|
+
createAuthenticatorClient() {
|
|
39
|
+
return new AuthenticatorClient(this.#config);
|
|
40
|
+
}
|
|
34
41
|
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { GuardFactory } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Authenticator client is used to create guard instances for
|
|
4
|
+
* testing. It passes a fake HTTPContext to the guards, so
|
|
5
|
+
* make sure to not call server side APIs that might be
|
|
6
|
+
* relying on a real HTTPContext instance
|
|
7
|
+
*/
|
|
8
|
+
export declare class AuthenticatorClient<KnownGuards extends Record<string, GuardFactory>> {
|
|
9
|
+
#private;
|
|
10
|
+
/**
|
|
11
|
+
* Name of the default guard
|
|
12
|
+
*/
|
|
13
|
+
get defaultGuard(): keyof KnownGuards;
|
|
14
|
+
constructor(config: {
|
|
15
|
+
default: keyof KnownGuards;
|
|
16
|
+
guards: KnownGuards;
|
|
17
|
+
});
|
|
18
|
+
/**
|
|
19
|
+
* Returns an instance of a known guard. Guards instances are
|
|
20
|
+
* cached during the lifecycle of an HTTP request.
|
|
21
|
+
*/
|
|
22
|
+
use<Guard extends keyof KnownGuards>(guard?: Guard): ReturnType<KnownGuards[Guard]>;
|
|
23
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* @adonisjs/auth
|
|
3
|
+
*
|
|
4
|
+
* (c) AdonisJS
|
|
5
|
+
*
|
|
6
|
+
* For the full copyright and license information, please view the LICENSE
|
|
7
|
+
* file that was distributed with this source code.
|
|
8
|
+
*/
|
|
9
|
+
import debug from './debug.js';
|
|
10
|
+
import { HttpContextFactory } from '@adonisjs/core/factories/http';
|
|
11
|
+
/**
|
|
12
|
+
* Authenticator client is used to create guard instances for
|
|
13
|
+
* testing. It passes a fake HTTPContext to the guards, so
|
|
14
|
+
* make sure to not call server side APIs that might be
|
|
15
|
+
* relying on a real HTTPContext instance
|
|
16
|
+
*/
|
|
17
|
+
export class AuthenticatorClient {
|
|
18
|
+
/**
|
|
19
|
+
* Registered guards
|
|
20
|
+
*/
|
|
21
|
+
#config;
|
|
22
|
+
/**
|
|
23
|
+
* Cache of guards
|
|
24
|
+
*/
|
|
25
|
+
#guardsCache = {};
|
|
26
|
+
/**
|
|
27
|
+
* Name of the default guard
|
|
28
|
+
*/
|
|
29
|
+
get defaultGuard() {
|
|
30
|
+
return this.#config.default;
|
|
31
|
+
}
|
|
32
|
+
constructor(config) {
|
|
33
|
+
this.#config = config;
|
|
34
|
+
debug('creating authenticator client. config %O', this.#config);
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Returns an instance of a known guard. Guards instances are
|
|
38
|
+
* cached during the lifecycle of an HTTP request.
|
|
39
|
+
*/
|
|
40
|
+
use(guard) {
|
|
41
|
+
const guardToUse = guard || this.#config.default;
|
|
42
|
+
/**
|
|
43
|
+
* Use cached copy if exists
|
|
44
|
+
*/
|
|
45
|
+
const cachedGuard = this.#guardsCache[guardToUse];
|
|
46
|
+
if (cachedGuard) {
|
|
47
|
+
debug('using guard from cache. name: "%s"', guardToUse);
|
|
48
|
+
return cachedGuard;
|
|
49
|
+
}
|
|
50
|
+
const guardFactory = this.#config.guards[guardToUse];
|
|
51
|
+
/**
|
|
52
|
+
* Construct guard and cache it
|
|
53
|
+
*/
|
|
54
|
+
debug('creating guard. name: "%s"', guardToUse);
|
|
55
|
+
const guardInstance = guardFactory(new HttpContextFactory().create());
|
|
56
|
+
this.#guardsCache[guardToUse] = guardInstance;
|
|
57
|
+
return guardInstance;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
@@ -12,6 +12,11 @@ export declare class AuthenticationException extends Exception {
|
|
|
12
12
|
* is unable to authenticate the request
|
|
13
13
|
*/
|
|
14
14
|
static E_INVALID_AUTH_SESSION(): AuthenticationException;
|
|
15
|
+
/**
|
|
16
|
+
* Raises authentication exception when session guard
|
|
17
|
+
* is unable to authenticate the request
|
|
18
|
+
*/
|
|
19
|
+
static E_INVALID_BASIC_AUTH_CREDENTIALS(): AuthenticationException;
|
|
15
20
|
guardDriverName: string;
|
|
16
21
|
redirectTo?: string;
|
|
17
22
|
identifier: string;
|
package/build/src/auth/errors.js
CHANGED
|
@@ -25,6 +25,17 @@ export class AuthenticationException extends Exception {
|
|
|
25
25
|
guardDriverName: 'session',
|
|
26
26
|
});
|
|
27
27
|
}
|
|
28
|
+
/**
|
|
29
|
+
* Raises authentication exception when session guard
|
|
30
|
+
* is unable to authenticate the request
|
|
31
|
+
*/
|
|
32
|
+
static E_INVALID_BASIC_AUTH_CREDENTIALS() {
|
|
33
|
+
return new AuthenticationException('Invalid basic auth credentials', {
|
|
34
|
+
code: 'E_INVALID_BASIC_AUTH_CREDENTIALS',
|
|
35
|
+
status: 401,
|
|
36
|
+
guardDriverName: 'basic_auth',
|
|
37
|
+
});
|
|
38
|
+
}
|
|
28
39
|
guardDriverName;
|
|
29
40
|
redirectTo;
|
|
30
41
|
identifier = 'auth.authenticate';
|
|
@@ -84,6 +95,12 @@ export class AuthenticationException extends Exception {
|
|
|
84
95
|
break;
|
|
85
96
|
}
|
|
86
97
|
},
|
|
98
|
+
basic_auth: (message, _, ctx) => {
|
|
99
|
+
ctx.response
|
|
100
|
+
.status(this.status)
|
|
101
|
+
.header('WWW-Authenticate', `Basic realm="Authenticate", charset="UTF-8"`)
|
|
102
|
+
.send(message);
|
|
103
|
+
},
|
|
87
104
|
};
|
|
88
105
|
/**
|
|
89
106
|
* Self handles the auth exception and converts it to an
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { PluginFn } from '@japa/runner/types';
|
|
2
|
+
import type { ApplicationService } from '@adonisjs/core/types';
|
|
3
|
+
import type { Authenticators, GuardContract, GuardFactory } from '../../types.js';
|
|
4
|
+
declare module '@japa/api-client' {
|
|
5
|
+
interface ApiRequest {
|
|
6
|
+
authData: {
|
|
7
|
+
guard: string;
|
|
8
|
+
user: unknown;
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* Login a user using the default authentication
|
|
12
|
+
* guard when making an API call
|
|
13
|
+
*/
|
|
14
|
+
loginAs(user: {
|
|
15
|
+
[K in keyof Authenticators]: Authenticators[K] extends GuardFactory ? ReturnType<Authenticators[K]> extends GuardContract<infer A> ? A : never : never;
|
|
16
|
+
}): this;
|
|
17
|
+
/**
|
|
18
|
+
* Define the authentication guard for login
|
|
19
|
+
*/
|
|
20
|
+
withGuard<K extends keyof Authenticators, Self extends ApiRequest>(this: Self, guard: K): {
|
|
21
|
+
/**
|
|
22
|
+
* Login a user using a specific auth guard
|
|
23
|
+
*/
|
|
24
|
+
loginAs(user: Authenticators[K] extends GuardFactory ? ReturnType<Authenticators[K]> extends GuardContract<infer A> ? A : never : never): Self;
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Auth API client to authenticate users when making
|
|
30
|
+
* HTTP requests using the Japa API client
|
|
31
|
+
*/
|
|
32
|
+
export declare const authApiClient: (app: ApplicationService) => PluginFn;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* @adonisjs/auth
|
|
3
|
+
*
|
|
4
|
+
* (c) AdonisJS
|
|
5
|
+
*
|
|
6
|
+
* For the full copyright and license information, please view the LICENSE
|
|
7
|
+
* file that was distributed with this source code.
|
|
8
|
+
*/
|
|
9
|
+
import { ApiClient, ApiRequest } from '@japa/api-client';
|
|
10
|
+
import debug from '../../debug.js';
|
|
11
|
+
/**
|
|
12
|
+
* Auth API client to authenticate users when making
|
|
13
|
+
* HTTP requests using the Japa API client
|
|
14
|
+
*/
|
|
15
|
+
export const authApiClient = (app) => {
|
|
16
|
+
const pluginFn = function () {
|
|
17
|
+
debug('installing auth api client plugin');
|
|
18
|
+
ApiRequest.macro('loginAs', function (user) {
|
|
19
|
+
this.authData = {
|
|
20
|
+
guard: '__default__',
|
|
21
|
+
user: user,
|
|
22
|
+
};
|
|
23
|
+
return this;
|
|
24
|
+
});
|
|
25
|
+
ApiRequest.macro('withGuard', function (guard) {
|
|
26
|
+
return {
|
|
27
|
+
loginAs: (user) => {
|
|
28
|
+
this.authData = {
|
|
29
|
+
guard,
|
|
30
|
+
user: user,
|
|
31
|
+
};
|
|
32
|
+
return this;
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
});
|
|
36
|
+
/**
|
|
37
|
+
* Hook into the request and login the user
|
|
38
|
+
*/
|
|
39
|
+
ApiClient.setup(async (request) => {
|
|
40
|
+
const auth = await app.container.make('auth.manager');
|
|
41
|
+
const authData = request['authData'];
|
|
42
|
+
if (!authData) {
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
const client = auth.createAuthenticatorClient();
|
|
46
|
+
const guard = authData.guard === '__default__' ? client.use() : client.use(authData.guard);
|
|
47
|
+
const requestData = await guard.authenticateAsClient(authData.user);
|
|
48
|
+
if (requestData.headers) {
|
|
49
|
+
debug('defining headers with api client request %O', requestData.headers);
|
|
50
|
+
request.headers(requestData.headers);
|
|
51
|
+
}
|
|
52
|
+
if (requestData.session) {
|
|
53
|
+
debug('defining session with api client request %O', requestData.session);
|
|
54
|
+
request.withSession(requestData.session);
|
|
55
|
+
}
|
|
56
|
+
if (requestData.cookies) {
|
|
57
|
+
debug('defining session with api client request %O', requestData.session);
|
|
58
|
+
request.cookies(requestData.cookies);
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
};
|
|
62
|
+
return pluginFn;
|
|
63
|
+
};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { PluginFn } from '@japa/runner/types';
|
|
2
|
+
import type { ApplicationService } from '@adonisjs/core/types';
|
|
3
|
+
import type { Authenticators, GuardContract, GuardFactory } from '../../types.js';
|
|
4
|
+
declare module 'playwright' {
|
|
5
|
+
interface BrowserContext {
|
|
6
|
+
/**
|
|
7
|
+
* Login a user using the default authentication
|
|
8
|
+
* guard when using the browser context to
|
|
9
|
+
* make page visits
|
|
10
|
+
*/
|
|
11
|
+
loginAs(user: {
|
|
12
|
+
[K in keyof Authenticators]: Authenticators[K] extends GuardFactory ? ReturnType<Authenticators[K]> extends GuardContract<infer A> ? A : never : never;
|
|
13
|
+
}): Promise<void>;
|
|
14
|
+
/**
|
|
15
|
+
* Define the authentication guard for login
|
|
16
|
+
*/
|
|
17
|
+
withGuard<K extends keyof Authenticators>(guard: K): {
|
|
18
|
+
/**
|
|
19
|
+
* Login a user using a specific auth guard
|
|
20
|
+
*/
|
|
21
|
+
loginAs(user: Authenticators[K] extends GuardFactory ? ReturnType<Authenticators[K]> extends GuardContract<infer A> ? A : never : never): Promise<void>;
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
export declare const authBrowserClient: (app: ApplicationService) => PluginFn;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* @adoniss/auth
|
|
3
|
+
*
|
|
4
|
+
* (c) AdonisJS
|
|
5
|
+
*
|
|
6
|
+
* For the full copyright and license information, please view the LICENSE
|
|
7
|
+
* file that was distributed with this source code.
|
|
8
|
+
*/
|
|
9
|
+
/// <reference types="@japa/plugin-adonisjs" />
|
|
10
|
+
/// <reference types="@adonisjs/session/plugins/browser_client" />
|
|
11
|
+
import { RuntimeException } from '@poppinss/utils';
|
|
12
|
+
import { decoratorsCollection } from '@japa/browser-client';
|
|
13
|
+
import debug from '../../debug.js';
|
|
14
|
+
export const authBrowserClient = (app) => {
|
|
15
|
+
const pluginFn = async function () {
|
|
16
|
+
debug('installing auth browser client plugin');
|
|
17
|
+
const auth = await app.container.make('auth.manager');
|
|
18
|
+
decoratorsCollection.register({
|
|
19
|
+
context(context) {
|
|
20
|
+
context.loginAs = async function (user) {
|
|
21
|
+
const client = auth.createAuthenticatorClient();
|
|
22
|
+
const guard = client.use();
|
|
23
|
+
const requestData = await guard.authenticateAsClient(user);
|
|
24
|
+
if (requestData.headers) {
|
|
25
|
+
throw new RuntimeException(`Cannot use "${guard.driverName}" guard with browser client`);
|
|
26
|
+
}
|
|
27
|
+
if (requestData.cookies) {
|
|
28
|
+
debug('defining cookies with browser context %O', requestData.cookies);
|
|
29
|
+
Object.keys(requestData.cookies).forEach((cookie) => {
|
|
30
|
+
context.setCookie(cookie, requestData.cookies[cookie]);
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
if (requestData.session) {
|
|
34
|
+
debug('defining session with browser context %O', requestData.session);
|
|
35
|
+
context.setSession(requestData.session);
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
},
|
|
39
|
+
});
|
|
40
|
+
};
|
|
41
|
+
return pluginFn;
|
|
42
|
+
};
|
|
@@ -2,6 +2,14 @@ import type { HttpContext } from '@adonisjs/core/http';
|
|
|
2
2
|
import type { ApplicationService, ConfigProvider } from '@adonisjs/core/types';
|
|
3
3
|
import type { AuthManager } from './auth_manager.js';
|
|
4
4
|
import type { GUARD_KNOWN_EVENTS } from './symbols.js';
|
|
5
|
+
/**
|
|
6
|
+
* The client response for authentication.
|
|
7
|
+
*/
|
|
8
|
+
export interface AuthClientResponse {
|
|
9
|
+
headers?: Record<string, any>;
|
|
10
|
+
cookies?: Record<string, any>;
|
|
11
|
+
session?: Record<string, any>;
|
|
12
|
+
}
|
|
5
13
|
/**
|
|
6
14
|
* A set of properties a guard must implement.
|
|
7
15
|
*/
|
|
@@ -30,6 +38,12 @@ export interface GuardContract<User> {
|
|
|
30
38
|
* exception
|
|
31
39
|
*/
|
|
32
40
|
check(): Promise<boolean>;
|
|
41
|
+
/**
|
|
42
|
+
* The method is used to authenticate the user as
|
|
43
|
+
* client. This method should return cookies,
|
|
44
|
+
* headers, or session state.
|
|
45
|
+
*/
|
|
46
|
+
authenticateAsClient(user: User): Promise<AuthClientResponse>;
|
|
33
47
|
/**
|
|
34
48
|
* Authenticates the current request and throws
|
|
35
49
|
* an exception if the request is not authenticated.
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { HttpContext } from '@adonisjs/core/http';
|
|
2
|
+
import type { ConfigProvider } from '@adonisjs/core/types';
|
|
3
|
+
import type { GuardConfigProvider } from '../../auth/types.js';
|
|
4
|
+
import type { UserProviderContract } from '../../core/types.js';
|
|
5
|
+
import { BasicAuthGuard } from './guard.js';
|
|
6
|
+
/**
|
|
7
|
+
* Helper function to configure the basic auth guard for
|
|
8
|
+
* authentication.
|
|
9
|
+
*
|
|
10
|
+
* This method returns a config builder, which internally
|
|
11
|
+
* returns a factory function to construct a guard
|
|
12
|
+
* during HTTP requests.
|
|
13
|
+
*/
|
|
14
|
+
export declare function basicAuthGuard<UserProvider extends UserProviderContract<unknown>>(config: {
|
|
15
|
+
provider: ConfigProvider<UserProvider>;
|
|
16
|
+
}): GuardConfigProvider<(ctx: HttpContext) => BasicAuthGuard<UserProvider>>;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* @adonisjs/auth
|
|
3
|
+
*
|
|
4
|
+
* (c) AdonisJS
|
|
5
|
+
*
|
|
6
|
+
* For the full copyright and license information, please view the LICENSE
|
|
7
|
+
* file that was distributed with this source code.
|
|
8
|
+
*/
|
|
9
|
+
import { configProvider } from '@adonisjs/core';
|
|
10
|
+
import { RuntimeException } from '@poppinss/utils';
|
|
11
|
+
import { BasicAuthGuard } from './guard.js';
|
|
12
|
+
/**
|
|
13
|
+
* Helper function to configure the basic auth guard for
|
|
14
|
+
* authentication.
|
|
15
|
+
*
|
|
16
|
+
* This method returns a config builder, which internally
|
|
17
|
+
* returns a factory function to construct a guard
|
|
18
|
+
* during HTTP requests.
|
|
19
|
+
*/
|
|
20
|
+
export function basicAuthGuard(config) {
|
|
21
|
+
return {
|
|
22
|
+
async resolver(guardName, app) {
|
|
23
|
+
const provider = await configProvider.resolve(app, config.provider);
|
|
24
|
+
if (!provider) {
|
|
25
|
+
throw new RuntimeException(`Invalid user provider defined on "${guardName}" guard`);
|
|
26
|
+
}
|
|
27
|
+
const emitter = await app.container.make('emitter');
|
|
28
|
+
/**
|
|
29
|
+
* Factory function needed by Authenticator to switch
|
|
30
|
+
* between guards and perform authentication
|
|
31
|
+
*/
|
|
32
|
+
return (ctx) => {
|
|
33
|
+
const guard = new BasicAuthGuard(guardName, ctx, provider);
|
|
34
|
+
return guard.withEmitter(emitter);
|
|
35
|
+
};
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import type { Emitter } from '@adonisjs/core/events';
|
|
2
|
+
import type { HttpContext } from '@adonisjs/core/http';
|
|
3
|
+
import type { BasicAuthGuardEvents } from './types.js';
|
|
4
|
+
import type { GuardContract } from '../../auth/types.js';
|
|
5
|
+
import type { UserProviderContract } from '../../core/types.js';
|
|
6
|
+
import { PROVIDER_REAL_USER, GUARD_KNOWN_EVENTS } from '../../auth/symbols.js';
|
|
7
|
+
/**
|
|
8
|
+
* Implementation of basic auth as an authentication guard
|
|
9
|
+
*/
|
|
10
|
+
export declare class BasicAuthGuard<UserProvider extends UserProviderContract<unknown>> implements GuardContract<UserProvider[typeof PROVIDER_REAL_USER]> {
|
|
11
|
+
#private;
|
|
12
|
+
[GUARD_KNOWN_EVENTS]: BasicAuthGuardEvents<UserProvider[typeof PROVIDER_REAL_USER]>;
|
|
13
|
+
/**
|
|
14
|
+
* Driver name of the guard
|
|
15
|
+
*/
|
|
16
|
+
driverName: 'basic_auth';
|
|
17
|
+
/**
|
|
18
|
+
* Whether or not the authentication has been attempted
|
|
19
|
+
* during the current request
|
|
20
|
+
*/
|
|
21
|
+
authenticationAttempted: boolean;
|
|
22
|
+
/**
|
|
23
|
+
* A boolean to know if the current request has
|
|
24
|
+
* been authenticated
|
|
25
|
+
*/
|
|
26
|
+
isAuthenticated: boolean;
|
|
27
|
+
/**
|
|
28
|
+
* Reference to an instance of the authenticated or logged-in
|
|
29
|
+
* user. The value only exists after calling one of the
|
|
30
|
+
* following methods.
|
|
31
|
+
*
|
|
32
|
+
* - authenticate
|
|
33
|
+
*
|
|
34
|
+
* You can use the "getUserOrFail" method to throw an exception if
|
|
35
|
+
* the request is not authenticated.
|
|
36
|
+
*/
|
|
37
|
+
user?: UserProvider[typeof PROVIDER_REAL_USER];
|
|
38
|
+
constructor(name: string, ctx: HttpContext, userProvider: UserProvider);
|
|
39
|
+
/**
|
|
40
|
+
* Register an event emitter to listen for global events for
|
|
41
|
+
* authentication lifecycle.
|
|
42
|
+
*/
|
|
43
|
+
withEmitter(emitter: Emitter<any>): this;
|
|
44
|
+
/**
|
|
45
|
+
* Returns an instance of the authenticated user. Or throws
|
|
46
|
+
* an exception if the request is not authenticated.
|
|
47
|
+
*/
|
|
48
|
+
getUserOrFail(): UserProvider[typeof PROVIDER_REAL_USER];
|
|
49
|
+
/**
|
|
50
|
+
* Verifies user credentials and returns an instance of
|
|
51
|
+
* the user or throws "E_INVALID_BASIC_AUTH_CREDENTIALS" exception.
|
|
52
|
+
*/
|
|
53
|
+
verifyCredentials(uid: string, password: string): Promise<UserProvider[typeof PROVIDER_REAL_USER]>;
|
|
54
|
+
/**
|
|
55
|
+
* Authenticates the current HTTP request for basic
|
|
56
|
+
* auth credentials
|
|
57
|
+
*/
|
|
58
|
+
authenticate(): Promise<UserProvider[typeof PROVIDER_REAL_USER]>;
|
|
59
|
+
/**
|
|
60
|
+
* Silently attempt to authenticate the user.
|
|
61
|
+
*
|
|
62
|
+
* The method returns a boolean indicating if the authentication
|
|
63
|
+
* succeeded or failed.
|
|
64
|
+
*/
|
|
65
|
+
check(): Promise<boolean>;
|
|
66
|
+
/**
|
|
67
|
+
* Not support
|
|
68
|
+
*/
|
|
69
|
+
authenticateAsClient(_: UserProvider[typeof PROVIDER_REAL_USER]): Promise<never>;
|
|
70
|
+
}
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* @adonisjs/auth
|
|
3
|
+
*
|
|
4
|
+
* (c) AdonisJS
|
|
5
|
+
*
|
|
6
|
+
* For the full copyright and license information, please view the LICENSE
|
|
7
|
+
* file that was distributed with this source code.
|
|
8
|
+
*/
|
|
9
|
+
import auth from 'basic-auth';
|
|
10
|
+
import { RuntimeException } from '@poppinss/utils';
|
|
11
|
+
import debug from '../../auth/debug.js';
|
|
12
|
+
import { AuthenticationException } from '../../auth/errors.js';
|
|
13
|
+
import { GUARD_KNOWN_EVENTS } from '../../auth/symbols.js';
|
|
14
|
+
/**
|
|
15
|
+
* Implementation of basic auth as an authentication guard
|
|
16
|
+
*/
|
|
17
|
+
export class BasicAuthGuard {
|
|
18
|
+
/**
|
|
19
|
+
* A unique name for the guard. It is used while
|
|
20
|
+
* emitting events
|
|
21
|
+
*/
|
|
22
|
+
#name;
|
|
23
|
+
/**
|
|
24
|
+
* Reference to the current HTTP context
|
|
25
|
+
*/
|
|
26
|
+
#ctx;
|
|
27
|
+
/**
|
|
28
|
+
* Provider to lookup user details
|
|
29
|
+
*/
|
|
30
|
+
#userProvider;
|
|
31
|
+
/**
|
|
32
|
+
* Emitter to emit events
|
|
33
|
+
*/
|
|
34
|
+
#emitter;
|
|
35
|
+
/**
|
|
36
|
+
* Driver name of the guard
|
|
37
|
+
*/
|
|
38
|
+
driverName = 'basic_auth';
|
|
39
|
+
/**
|
|
40
|
+
* Whether or not the authentication has been attempted
|
|
41
|
+
* during the current request
|
|
42
|
+
*/
|
|
43
|
+
authenticationAttempted = false;
|
|
44
|
+
/**
|
|
45
|
+
* A boolean to know if the current request has
|
|
46
|
+
* been authenticated
|
|
47
|
+
*/
|
|
48
|
+
isAuthenticated = false;
|
|
49
|
+
/**
|
|
50
|
+
* Reference to an instance of the authenticated or logged-in
|
|
51
|
+
* user. The value only exists after calling one of the
|
|
52
|
+
* following methods.
|
|
53
|
+
*
|
|
54
|
+
* - authenticate
|
|
55
|
+
*
|
|
56
|
+
* You can use the "getUserOrFail" method to throw an exception if
|
|
57
|
+
* the request is not authenticated.
|
|
58
|
+
*/
|
|
59
|
+
user;
|
|
60
|
+
constructor(name, ctx, userProvider) {
|
|
61
|
+
this.#ctx = ctx;
|
|
62
|
+
this.#name = name;
|
|
63
|
+
this.#userProvider = userProvider;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Notifies about authentication failure and throws the exception
|
|
67
|
+
*/
|
|
68
|
+
#authenticationFailed(error) {
|
|
69
|
+
if (this.#emitter) {
|
|
70
|
+
this.#emitter.emit('basic_auth:authentication_failed', {
|
|
71
|
+
guardName: this.#name,
|
|
72
|
+
error,
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
throw error;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Register an event emitter to listen for global events for
|
|
79
|
+
* authentication lifecycle.
|
|
80
|
+
*/
|
|
81
|
+
withEmitter(emitter) {
|
|
82
|
+
this.#emitter = emitter;
|
|
83
|
+
return this;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Returns an instance of the authenticated user. Or throws
|
|
87
|
+
* an exception if the request is not authenticated.
|
|
88
|
+
*/
|
|
89
|
+
getUserOrFail() {
|
|
90
|
+
if (!this.user) {
|
|
91
|
+
throw AuthenticationException.E_INVALID_BASIC_AUTH_CREDENTIALS();
|
|
92
|
+
}
|
|
93
|
+
return this.user;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Verifies user credentials and returns an instance of
|
|
97
|
+
* the user or throws "E_INVALID_BASIC_AUTH_CREDENTIALS" exception.
|
|
98
|
+
*/
|
|
99
|
+
async verifyCredentials(uid, password) {
|
|
100
|
+
debug('basic_auth_guard: attempting to verify credentials for uid "%s"', uid);
|
|
101
|
+
/**
|
|
102
|
+
* Attempt to find a user by the uid and raise
|
|
103
|
+
* error when unable to find one
|
|
104
|
+
*/
|
|
105
|
+
const providerUser = await this.#userProvider.findByUid(uid);
|
|
106
|
+
if (!providerUser) {
|
|
107
|
+
this.#authenticationFailed(AuthenticationException.E_INVALID_BASIC_AUTH_CREDENTIALS());
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Raise error when unable to verify password
|
|
111
|
+
*/
|
|
112
|
+
const user = providerUser.getOriginal();
|
|
113
|
+
/**
|
|
114
|
+
* Raise error when unable to verify password
|
|
115
|
+
*/
|
|
116
|
+
if (!(await providerUser.verifyPassword(password))) {
|
|
117
|
+
this.#authenticationFailed(AuthenticationException.E_INVALID_BASIC_AUTH_CREDENTIALS());
|
|
118
|
+
}
|
|
119
|
+
return user;
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Authenticates the current HTTP request for basic
|
|
123
|
+
* auth credentials
|
|
124
|
+
*/
|
|
125
|
+
async authenticate() {
|
|
126
|
+
/**
|
|
127
|
+
* Avoid re-authenticating when already authenticated
|
|
128
|
+
*/
|
|
129
|
+
if (this.authenticationAttempted) {
|
|
130
|
+
return this.getUserOrFail();
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Beginning authentication attempt
|
|
134
|
+
*/
|
|
135
|
+
this.authenticationAttempted = true;
|
|
136
|
+
if (this.#emitter) {
|
|
137
|
+
this.#emitter.emit('basic_auth:authentication_attempted', {
|
|
138
|
+
guardName: this.#name,
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Fetch credentials from the header
|
|
143
|
+
*/
|
|
144
|
+
const credentials = auth(this.#ctx.request.request);
|
|
145
|
+
if (!credentials) {
|
|
146
|
+
this.#authenticationFailed(AuthenticationException.E_INVALID_BASIC_AUTH_CREDENTIALS());
|
|
147
|
+
}
|
|
148
|
+
debug('basic_auth_guard: authenticating user using credentials');
|
|
149
|
+
/**
|
|
150
|
+
* Verifying user credentials
|
|
151
|
+
*/
|
|
152
|
+
this.user = await this.verifyCredentials(credentials.name, credentials.pass);
|
|
153
|
+
this.isAuthenticated = true;
|
|
154
|
+
debug('basic_auth_guard: marking user as authenticated');
|
|
155
|
+
if (this.#emitter) {
|
|
156
|
+
this.#emitter.emit('basic_auth:authentication_succeeded', {
|
|
157
|
+
guardName: this.#name,
|
|
158
|
+
user: this.user,
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Return user
|
|
163
|
+
*/
|
|
164
|
+
return this.getUserOrFail();
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Silently attempt to authenticate the user.
|
|
168
|
+
*
|
|
169
|
+
* The method returns a boolean indicating if the authentication
|
|
170
|
+
* succeeded or failed.
|
|
171
|
+
*/
|
|
172
|
+
async check() {
|
|
173
|
+
try {
|
|
174
|
+
await this.authenticate();
|
|
175
|
+
return true;
|
|
176
|
+
}
|
|
177
|
+
catch (error) {
|
|
178
|
+
if (error instanceof AuthenticationException) {
|
|
179
|
+
return false;
|
|
180
|
+
}
|
|
181
|
+
throw error;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Not support
|
|
186
|
+
*/
|
|
187
|
+
async authenticateAsClient(_) {
|
|
188
|
+
throw new RuntimeException('Cannot authenticate as a client when using basic auth');
|
|
189
|
+
}
|
|
190
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* @adonisjs/auth
|
|
3
|
+
*
|
|
4
|
+
* (c) AdonisJS
|
|
5
|
+
*
|
|
6
|
+
* For the full copyright and license information, please view the LICENSE
|
|
7
|
+
* file that was distributed with this source code.
|
|
8
|
+
*/
|
|
9
|
+
export { BasicAuthGuard } from './guard.js';
|
|
10
|
+
export { basicAuthGuard } from './define_config.js';
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { Exception } from '@poppinss/utils';
|
|
2
|
+
/**
|
|
3
|
+
* Events emitted by the basic auth guard
|
|
4
|
+
*/
|
|
5
|
+
export type BasicAuthGuardEvents<User> = {
|
|
6
|
+
/**
|
|
7
|
+
* The event is emitted when the user credentials
|
|
8
|
+
* have been verified successfully.
|
|
9
|
+
*/
|
|
10
|
+
'basic_auth:credentials_verified': {
|
|
11
|
+
guardName: string;
|
|
12
|
+
uid: string;
|
|
13
|
+
user: User;
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* Attempting to authenticate the user
|
|
17
|
+
*/
|
|
18
|
+
'basic_auth:authentication_attempted': {
|
|
19
|
+
guardName: string;
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* Authentication was successful
|
|
23
|
+
*/
|
|
24
|
+
'basic_auth:authentication_succeeded': {
|
|
25
|
+
guardName: string;
|
|
26
|
+
user: User;
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* Authentication failed
|
|
30
|
+
*/
|
|
31
|
+
'basic_auth:authentication_failed': {
|
|
32
|
+
guardName: string;
|
|
33
|
+
error: Exception;
|
|
34
|
+
};
|
|
35
|
+
};
|
|
@@ -113,4 +113,11 @@ export declare class SessionGuard<UserProvider extends SessionUserProviderContra
|
|
|
113
113
|
* Logout user and revoke remember me token (if any)
|
|
114
114
|
*/
|
|
115
115
|
logout(): Promise<void>;
|
|
116
|
+
/**
|
|
117
|
+
* Returns the session state for the user to be
|
|
118
|
+
* logged-in as a client
|
|
119
|
+
*/
|
|
120
|
+
authenticateAsClient(user: UserProvider[typeof PROVIDER_REAL_USER]): Promise<{
|
|
121
|
+
session: Record<string, string | number>;
|
|
122
|
+
}>;
|
|
116
123
|
}
|
|
@@ -327,6 +327,7 @@ export class SessionGuard {
|
|
|
327
327
|
if (!providerUser) {
|
|
328
328
|
this.#authenticationFailed(AuthenticationException.E_INVALID_AUTH_SESSION(), session.sessionId);
|
|
329
329
|
}
|
|
330
|
+
debug('session_guard: marking user with id "%s" as authenticated', providerUser.getId());
|
|
330
331
|
this.user = providerUser.getOriginal();
|
|
331
332
|
this.isAuthenticated = true;
|
|
332
333
|
this.isLoggedOut = false;
|
|
@@ -390,6 +391,7 @@ export class SessionGuard {
|
|
|
390
391
|
debug('session_guard: marking user with id "%s" as logged in from remember me cookie', userId);
|
|
391
392
|
session.put(this.sessionKeyName, userId);
|
|
392
393
|
session.regenerate();
|
|
394
|
+
debug('session_guard: marking user with id "%s" as authenticated', userId);
|
|
393
395
|
this.user = providerUser.getOriginal();
|
|
394
396
|
this.isAuthenticated = true;
|
|
395
397
|
this.isLoggedOut = false;
|
|
@@ -491,4 +493,18 @@ export class SessionGuard {
|
|
|
491
493
|
debug('session_auth: deleting remember me token');
|
|
492
494
|
await this.#rememberMeTokenProvider.deleteTokenBySeries(decodedToken.series);
|
|
493
495
|
}
|
|
496
|
+
/**
|
|
497
|
+
* Returns the session state for the user to be
|
|
498
|
+
* logged-in as a client
|
|
499
|
+
*/
|
|
500
|
+
async authenticateAsClient(user) {
|
|
501
|
+
const providerUser = await this.#userProvider.createUserForGuard(user);
|
|
502
|
+
const userId = providerUser.getId();
|
|
503
|
+
debug('session_guard: returning client session for user id "%s"', userId);
|
|
504
|
+
return {
|
|
505
|
+
session: {
|
|
506
|
+
[this.sessionKeyName]: userId,
|
|
507
|
+
},
|
|
508
|
+
};
|
|
509
|
+
}
|
|
494
510
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adonisjs/auth",
|
|
3
|
-
"version": "9.0.0-
|
|
3
|
+
"version": "9.0.0-7",
|
|
4
4
|
"description": "Official authentication provider for Adonis framework",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "build/index.js",
|
|
@@ -22,6 +22,8 @@
|
|
|
22
22
|
".": "./build/index.js",
|
|
23
23
|
"./types": "./build/src/auth/types.js",
|
|
24
24
|
"./auth_provider": "./build/providers/auth_provider.js",
|
|
25
|
+
"./plugins/api_client": "./build/src/auth/plugins/japa/api_client.js",
|
|
26
|
+
"./plugins/browser_client": "./build/src/auth/plugins/japa/browser_client.js",
|
|
25
27
|
"./services/main": "./build/services/auth.js",
|
|
26
28
|
"./core/token": "./build/src/core/token.js",
|
|
27
29
|
"./core/guard_user": "./build/src/core/guard_user.js",
|
|
@@ -29,6 +31,7 @@
|
|
|
29
31
|
"./core/token_providers/*": "./build/src/core/token_providers/*.js",
|
|
30
32
|
"./types/core": "./build/src/core/types.js",
|
|
31
33
|
"./session": "./build/src/guards/session/main.js",
|
|
34
|
+
"./basic_auth": "./build/src/guards/basic_auth/main.js",
|
|
32
35
|
"./initialize_auth_middleware": "./build/src/auth/middleware/initialize_auth_middleware.js",
|
|
33
36
|
"./types/session": "./build/src/guards/session/types.js"
|
|
34
37
|
},
|
|
@@ -74,12 +77,16 @@
|
|
|
74
77
|
"@adonisjs/tsconfig": "^1.1.8",
|
|
75
78
|
"@commitlint/cli": "^18.0.0",
|
|
76
79
|
"@commitlint/config-conventional": "^18.0.0",
|
|
80
|
+
"@japa/api-client": "^2.0.0",
|
|
77
81
|
"@japa/assert": "^2.0.0",
|
|
82
|
+
"@japa/browser-client": "^2.0.0",
|
|
78
83
|
"@japa/expect-type": "^2.0.0",
|
|
79
84
|
"@japa/file-system": "^2.0.0",
|
|
85
|
+
"@japa/plugin-adonisjs": "^2.0.0",
|
|
80
86
|
"@japa/runner": "^3.0.4",
|
|
81
87
|
"@japa/snapshot": "^2.0.0",
|
|
82
88
|
"@swc/core": "1.3.82",
|
|
89
|
+
"@types/basic-auth": "^1.1.5",
|
|
83
90
|
"@types/luxon": "^3.3.3",
|
|
84
91
|
"@types/node": "^20.8.7",
|
|
85
92
|
"@types/set-cookie-parser": "^2.4.5",
|
|
@@ -92,6 +99,7 @@
|
|
|
92
99
|
"husky": "^8.0.3",
|
|
93
100
|
"luxon": "^3.4.3",
|
|
94
101
|
"np": "^8.0.4",
|
|
102
|
+
"playwright": "^1.39.0",
|
|
95
103
|
"prettier": "^3.0.3",
|
|
96
104
|
"set-cookie-parser": "^2.6.0",
|
|
97
105
|
"sqlite3": "^5.1.6",
|
|
@@ -129,12 +137,16 @@
|
|
|
129
137
|
]
|
|
130
138
|
},
|
|
131
139
|
"dependencies": {
|
|
132
|
-
"@poppinss/utils": "^6.5.0"
|
|
140
|
+
"@poppinss/utils": "^6.5.0",
|
|
141
|
+
"basic-auth": "^2.0.1"
|
|
133
142
|
},
|
|
134
143
|
"peerDependencies": {
|
|
135
|
-
"@adonisjs/core": "^6.1.5-
|
|
144
|
+
"@adonisjs/core": "^6.1.5-31",
|
|
136
145
|
"@adonisjs/lucid": "^19.0.0-3",
|
|
137
|
-
"@adonisjs/session": "^7.0.0-13"
|
|
146
|
+
"@adonisjs/session": "^7.0.0-13",
|
|
147
|
+
"@japa/api-client": "^2.0.0",
|
|
148
|
+
"@japa/browser-client": "^2.0.0",
|
|
149
|
+
"@japa/plugin-adonisjs": "^2.0.0"
|
|
138
150
|
},
|
|
139
151
|
"peerDependenciesMeta": {
|
|
140
152
|
"@adonisjs/lucid": {
|
|
@@ -142,6 +154,15 @@
|
|
|
142
154
|
},
|
|
143
155
|
"@adonisjs/session": {
|
|
144
156
|
"optional": true
|
|
157
|
+
},
|
|
158
|
+
"@japa/api-client": {
|
|
159
|
+
"optional": true
|
|
160
|
+
},
|
|
161
|
+
"@japa/browser-client": {
|
|
162
|
+
"optional": true
|
|
163
|
+
},
|
|
164
|
+
"@japa/plugin-adonisjs": {
|
|
165
|
+
"optional": true
|
|
145
166
|
}
|
|
146
167
|
}
|
|
147
168
|
}
|