@theshelf/authentication 0.1.0 → 0.2.1
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/README.md +30 -32
- package/dist/ConnectionManager.d.ts +9 -0
- package/dist/ConnectionManager.js +53 -0
- package/dist/IdentityProvider.d.ts +15 -0
- package/dist/IdentityProvider.js +34 -0
- package/dist/definitions/constants.d.ts +7 -0
- package/dist/definitions/constants.js +6 -0
- package/dist/definitions/interfaces.d.ts +1 -1
- package/dist/definitions/types.d.ts +1 -1
- package/dist/{implementations/google → drivers}/Google.d.ts +3 -3
- package/dist/{implementations/google → drivers}/Google.js +8 -4
- package/dist/{implementations/openid → drivers}/OpenID.d.ts +3 -3
- package/dist/{implementations/openid → drivers}/OpenID.js +8 -4
- package/dist/errors/AuthenticationError.d.ts +1 -0
- package/dist/errors/AuthenticationError.js +7 -0
- package/dist/errors/RefreshFailed.d.ts +3 -0
- package/dist/errors/RefreshFailed.js +3 -0
- package/dist/index.d.ts +5 -4
- package/dist/index.js +3 -4
- package/package.json +2 -2
- package/dist/errors/UnknownImplementation.d.ts +0 -4
- package/dist/errors/UnknownImplementation.js +0 -6
- package/dist/implementation.d.ts +0 -3
- package/dist/implementation.js +0 -14
- package/dist/implementations/google/create.d.ts +0 -2
- package/dist/implementations/google/create.js +0 -10
- package/dist/implementations/openid/create.d.ts +0 -2
- package/dist/implementations/openid/create.js +0 -9
package/README.md
CHANGED
|
@@ -18,59 +18,57 @@ This package is based on the following authentication flow:
|
|
|
18
18
|
npm install @theshelf/authentication
|
|
19
19
|
```
|
|
20
20
|
|
|
21
|
-
##
|
|
21
|
+
## Drivers
|
|
22
22
|
|
|
23
|
-
Currently, there
|
|
23
|
+
Currently, there are two drivers available:
|
|
24
24
|
|
|
25
25
|
* **OpenID** - persistent document storage.
|
|
26
26
|
* **Google** - authentication via Google accounts
|
|
27
27
|
|
|
28
|
-
##
|
|
28
|
+
## How to use
|
|
29
29
|
|
|
30
|
-
The
|
|
30
|
+
The basic set up looks like this.
|
|
31
31
|
|
|
32
|
-
```
|
|
33
|
-
|
|
34
|
-
AUTHENTICATION_CLIENT_URI="https://application.com/authenticate"
|
|
35
|
-
```
|
|
32
|
+
```ts
|
|
33
|
+
import IdentityProvider, { OpenIDDriver | GoogleDriver as SelectedDriver } from '@theshelf/authentication';
|
|
36
34
|
|
|
37
|
-
|
|
35
|
+
const driver = new SelectedDriver(/* configuration */);
|
|
36
|
+
const identityProvider = new IdentityProvider(driver);
|
|
38
37
|
|
|
39
|
-
|
|
40
|
-
OPENID_ISSUER="https://identityprovider.com"
|
|
41
|
-
OPENID_CLIENT_ID="openid"
|
|
42
|
-
OPENID_CLIENT_SECRET=""
|
|
43
|
-
OPENID_REDIRECT_PATH="https://application.com/login"
|
|
44
|
-
OPENID_ALLOW_INSECURE_REQUESTS=false
|
|
38
|
+
// Perform operations with the identityProvider instance
|
|
45
39
|
```
|
|
46
40
|
|
|
47
|
-
|
|
41
|
+
### Configuration
|
|
48
42
|
|
|
49
|
-
|
|
50
|
-
GOOGLE_ISSUER="https://accounts.google.com"
|
|
51
|
-
GOOGLE_CLIENT_ID="google-client-id.apps.googleusercontent.com"
|
|
52
|
-
GOOGLE_CLIENT_SECRET=""
|
|
53
|
-
GOOGLE_REDIRECT_PATH="https://application.com/login"
|
|
54
|
-
GOOGLE_ACCESS_TYPE="offline"
|
|
55
|
-
GOOGLE_ORGANIZATION_DOMAIN="yourdomain.com"
|
|
56
|
-
```
|
|
57
|
-
|
|
58
|
-
The ACCESS_TYPE can be either `online` or `offline`. The default is `offline`, which provides refresh tokens. The ORGANIZATION_DOMAIN can be used to restrict login to a specific Google Workspace domain.
|
|
43
|
+
#### OpenID driver
|
|
59
44
|
|
|
60
|
-
|
|
45
|
+
```ts
|
|
46
|
+
type OpenIDConfiguration = {
|
|
47
|
+
issuer: string; // URL to the provider
|
|
48
|
+
clientId: string; // provided by the provider
|
|
49
|
+
clientSecret: string; // provided by the provider
|
|
50
|
+
redirectPath: string; // e.g. "https://application.com/login"
|
|
51
|
+
allowInsecureRequests: boolean; // only set to false in development
|
|
52
|
+
};
|
|
53
|
+
```
|
|
61
54
|
|
|
62
|
-
|
|
55
|
+
#### Google driver
|
|
63
56
|
|
|
64
57
|
```ts
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
58
|
+
type GoogleConfiguration = {
|
|
59
|
+
issuer: string; // "https://accounts.google.com"
|
|
60
|
+
clientId: string; // provided by Google
|
|
61
|
+
clientSecret: string; // provided by Google
|
|
62
|
+
redirectPath: string; // e.g. "https://application.com/login"
|
|
63
|
+
accessType: string; // "online" | "offline"
|
|
64
|
+
organizationDomain: string; // "application.com"
|
|
65
|
+
};
|
|
68
66
|
```
|
|
69
67
|
|
|
70
68
|
### Operations
|
|
71
69
|
|
|
72
70
|
```ts
|
|
73
|
-
import
|
|
71
|
+
import { Session } from '@theshelf/authentication';
|
|
74
72
|
|
|
75
73
|
// Open connection
|
|
76
74
|
await identityProvider.connect();
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { ConnectionState } from './definitions/constants.js';
|
|
2
|
+
import type { Driver } from './definitions/interfaces.js';
|
|
3
|
+
export default class ConnectionManager {
|
|
4
|
+
#private;
|
|
5
|
+
constructor(driver: Driver);
|
|
6
|
+
get state(): ConnectionState;
|
|
7
|
+
connect(): Promise<void>;
|
|
8
|
+
disconnect(): Promise<void>;
|
|
9
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { ConnectionStates } from './definitions/constants.js';
|
|
2
|
+
export default class ConnectionManager {
|
|
3
|
+
#driver;
|
|
4
|
+
#state = ConnectionStates.DISCONNECTED;
|
|
5
|
+
#connectPromise;
|
|
6
|
+
#disconnectPromise;
|
|
7
|
+
constructor(driver) {
|
|
8
|
+
this.#driver = driver;
|
|
9
|
+
}
|
|
10
|
+
get state() { return this.#state; }
|
|
11
|
+
async connect() {
|
|
12
|
+
if (this.#connectPromise !== undefined) {
|
|
13
|
+
return this.#connectPromise;
|
|
14
|
+
}
|
|
15
|
+
if (this.#state !== ConnectionStates.DISCONNECTED) {
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
this.#state = ConnectionStates.CONNECTING;
|
|
19
|
+
try {
|
|
20
|
+
this.#connectPromise = this.#driver.connect();
|
|
21
|
+
await this.#connectPromise;
|
|
22
|
+
this.#state = ConnectionStates.CONNECTED;
|
|
23
|
+
}
|
|
24
|
+
catch (error) {
|
|
25
|
+
this.#state = ConnectionStates.DISCONNECTED;
|
|
26
|
+
throw error;
|
|
27
|
+
}
|
|
28
|
+
finally {
|
|
29
|
+
this.#connectPromise = undefined;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
async disconnect() {
|
|
33
|
+
if (this.#disconnectPromise !== undefined) {
|
|
34
|
+
return this.#disconnectPromise;
|
|
35
|
+
}
|
|
36
|
+
if (this.#state !== ConnectionStates.CONNECTED) {
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
this.#state = ConnectionStates.DISCONNECTING;
|
|
40
|
+
try {
|
|
41
|
+
this.#disconnectPromise = this.#driver.disconnect();
|
|
42
|
+
await this.#disconnectPromise;
|
|
43
|
+
this.#state = ConnectionStates.DISCONNECTED;
|
|
44
|
+
}
|
|
45
|
+
catch (error) {
|
|
46
|
+
this.#state = ConnectionStates.CONNECTED;
|
|
47
|
+
throw error;
|
|
48
|
+
}
|
|
49
|
+
finally {
|
|
50
|
+
this.#disconnectPromise = undefined;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { ConnectionState } from './definitions/constants.js';
|
|
2
|
+
import type { Driver } from './definitions/interfaces.js';
|
|
3
|
+
import type { Session } from './definitions/types.js';
|
|
4
|
+
export default class IdentityProvider implements Driver {
|
|
5
|
+
#private;
|
|
6
|
+
constructor(driver: Driver);
|
|
7
|
+
get connectionState(): ConnectionState;
|
|
8
|
+
get connected(): boolean;
|
|
9
|
+
connect(): Promise<void>;
|
|
10
|
+
disconnect(): Promise<void>;
|
|
11
|
+
getLoginUrl(origin: string): Promise<string>;
|
|
12
|
+
login(origin: string, data: Record<string, unknown>): Promise<Session>;
|
|
13
|
+
refresh(session: Session): Promise<Session>;
|
|
14
|
+
logout(session: Session): Promise<void>;
|
|
15
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { ConnectionStates } from './definitions/constants.js';
|
|
2
|
+
import ConnectionManager from './ConnectionManager.js';
|
|
3
|
+
export default class IdentityProvider {
|
|
4
|
+
#driver;
|
|
5
|
+
#connectionManager;
|
|
6
|
+
constructor(driver) {
|
|
7
|
+
this.#driver = driver;
|
|
8
|
+
this.#connectionManager = new ConnectionManager(driver);
|
|
9
|
+
}
|
|
10
|
+
get connectionState() {
|
|
11
|
+
return this.#connectionManager.state;
|
|
12
|
+
}
|
|
13
|
+
get connected() {
|
|
14
|
+
return this.connectionState === ConnectionStates.CONNECTED;
|
|
15
|
+
}
|
|
16
|
+
connect() {
|
|
17
|
+
return this.#connectionManager.connect();
|
|
18
|
+
}
|
|
19
|
+
disconnect() {
|
|
20
|
+
return this.#connectionManager.disconnect();
|
|
21
|
+
}
|
|
22
|
+
getLoginUrl(origin) {
|
|
23
|
+
return this.#driver.getLoginUrl(origin);
|
|
24
|
+
}
|
|
25
|
+
login(origin, data) {
|
|
26
|
+
return this.#driver.login(origin, data);
|
|
27
|
+
}
|
|
28
|
+
refresh(session) {
|
|
29
|
+
return this.#driver.refresh(session);
|
|
30
|
+
}
|
|
31
|
+
logout(session) {
|
|
32
|
+
return this.#driver.logout(session);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export declare const ConnectionStates: {
|
|
2
|
+
readonly DISCONNECTED: "DISCONNECTED";
|
|
3
|
+
readonly DISCONNECTING: "DISCONNECTING";
|
|
4
|
+
readonly CONNECTING: "CONNECTING";
|
|
5
|
+
readonly CONNECTED: "CONNECTED";
|
|
6
|
+
};
|
|
7
|
+
export type ConnectionState = typeof ConnectionStates[keyof typeof ConnectionStates];
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import type { Session } from '
|
|
1
|
+
import type { Driver } from '../definitions/interfaces.js';
|
|
2
|
+
import type { Session } from '../definitions/types.js';
|
|
3
3
|
type GoogleConfiguration = {
|
|
4
4
|
issuer: string;
|
|
5
5
|
clientId: string;
|
|
@@ -8,7 +8,7 @@ type GoogleConfiguration = {
|
|
|
8
8
|
accessType: string;
|
|
9
9
|
organizationDomain: string;
|
|
10
10
|
};
|
|
11
|
-
export default class OpenID implements
|
|
11
|
+
export default class OpenID implements Driver {
|
|
12
12
|
#private;
|
|
13
13
|
constructor(configuration: GoogleConfiguration);
|
|
14
14
|
get connected(): boolean;
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { authorizationCodeGrant, buildAuthorizationUrl, calculatePKCECodeChallenge, discovery, fetchUserInfo, randomNonce, randomPKCECodeVerifier, refreshTokenGrant, tokenRevocation } from 'openid-client';
|
|
2
2
|
import crypto from 'node:crypto';
|
|
3
|
-
import LoginFailed from '
|
|
4
|
-
import
|
|
3
|
+
import LoginFailed from '../errors/LoginFailed.js';
|
|
4
|
+
import RefreshFailed from '../errors/RefreshFailed.js';
|
|
5
|
+
import NotConnected from '../errors/NotConnected.js';
|
|
5
6
|
const SECRET = crypto.randomUUID() + crypto.randomUUID();
|
|
6
7
|
const TTL = 30000;
|
|
7
8
|
export default class OpenID {
|
|
@@ -51,7 +52,7 @@ export default class OpenID {
|
|
|
51
52
|
const clientConfiguration = this.#getClientConfiguration();
|
|
52
53
|
const url = new URL(this.#providerConfiguration.redirectPath, origin);
|
|
53
54
|
for (const [key, value] of Object.entries(data)) {
|
|
54
|
-
url.searchParams.set(key, value);
|
|
55
|
+
url.searchParams.set(key, String(value));
|
|
55
56
|
}
|
|
56
57
|
const payload = this.#getPayload(data.state);
|
|
57
58
|
const tokens = await authorizationCodeGrant(clientConfiguration, url, {
|
|
@@ -80,6 +81,9 @@ export default class OpenID {
|
|
|
80
81
|
};
|
|
81
82
|
}
|
|
82
83
|
async refresh(session) {
|
|
84
|
+
if (session.refreshToken === undefined) {
|
|
85
|
+
throw new RefreshFailed('Missing refresh token');
|
|
86
|
+
}
|
|
83
87
|
const config = this.#getClientConfiguration();
|
|
84
88
|
const tokens = await refreshTokenGrant(config, session.refreshToken);
|
|
85
89
|
const claims = this.#getClaims(tokens);
|
|
@@ -94,7 +98,7 @@ export default class OpenID {
|
|
|
94
98
|
}
|
|
95
99
|
logout(session) {
|
|
96
100
|
const config = this.#getClientConfiguration();
|
|
97
|
-
return tokenRevocation(config, session.refreshToken);
|
|
101
|
+
return tokenRevocation(config, session.refreshToken ?? session.accessToken);
|
|
98
102
|
}
|
|
99
103
|
#getClientConfiguration() {
|
|
100
104
|
if (this.#clientConfiguration === undefined) {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import type { Session } from '
|
|
1
|
+
import type { Driver } from '../definitions/interfaces.js';
|
|
2
|
+
import type { Session } from '../definitions/types.js';
|
|
3
3
|
type OpenIDConfiguration = {
|
|
4
4
|
issuer: string;
|
|
5
5
|
clientId: string;
|
|
@@ -7,7 +7,7 @@ type OpenIDConfiguration = {
|
|
|
7
7
|
redirectPath: string;
|
|
8
8
|
allowInsecureRequests: boolean;
|
|
9
9
|
};
|
|
10
|
-
export default class OpenID implements
|
|
10
|
+
export default class OpenID implements Driver {
|
|
11
11
|
#private;
|
|
12
12
|
constructor(configuration: OpenIDConfiguration);
|
|
13
13
|
get connected(): boolean;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { allowInsecureRequests, authorizationCodeGrant, buildAuthorizationUrlWithPAR, calculatePKCECodeChallenge, discovery, fetchUserInfo, randomPKCECodeVerifier, refreshTokenGrant, tokenRevocation } from 'openid-client';
|
|
2
|
-
import LoginFailed from '
|
|
3
|
-
import
|
|
2
|
+
import LoginFailed from '../errors/LoginFailed.js';
|
|
3
|
+
import RefreshFailed from '../errors/RefreshFailed.js';
|
|
4
|
+
import NotConnected from '../errors/NotConnected.js';
|
|
4
5
|
export default class OpenID {
|
|
5
6
|
#providerConfiguration;
|
|
6
7
|
#clientConfiguration;
|
|
@@ -40,7 +41,7 @@ export default class OpenID {
|
|
|
40
41
|
const clientConfiguration = this.#getClientConfiguration();
|
|
41
42
|
const url = new URL(this.#providerConfiguration.redirectPath, origin);
|
|
42
43
|
for (const [key, value] of Object.entries(data)) {
|
|
43
|
-
url.searchParams.set(key, value);
|
|
44
|
+
url.searchParams.set(key, String(value));
|
|
44
45
|
}
|
|
45
46
|
const tokens = await authorizationCodeGrant(clientConfiguration, url, {
|
|
46
47
|
pkceCodeVerifier: this.#codeVerifier,
|
|
@@ -66,6 +67,9 @@ export default class OpenID {
|
|
|
66
67
|
};
|
|
67
68
|
}
|
|
68
69
|
async refresh(session) {
|
|
70
|
+
if (session.refreshToken === undefined) {
|
|
71
|
+
throw new RefreshFailed('Missing refresh token');
|
|
72
|
+
}
|
|
69
73
|
const config = this.#getClientConfiguration();
|
|
70
74
|
const tokens = await refreshTokenGrant(config, session.refreshToken);
|
|
71
75
|
const claims = this.#getClaims(tokens);
|
|
@@ -80,7 +84,7 @@ export default class OpenID {
|
|
|
80
84
|
}
|
|
81
85
|
logout(session) {
|
|
82
86
|
const config = this.#getClientConfiguration();
|
|
83
|
-
return tokenRevocation(config, session.refreshToken);
|
|
87
|
+
return tokenRevocation(config, session.refreshToken ?? session.accessToken);
|
|
84
88
|
}
|
|
85
89
|
#getClientConfiguration() {
|
|
86
90
|
if (this.#clientConfiguration === undefined) {
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
export * from './definitions/interfaces.js';
|
|
2
|
-
export * from './definitions/types.js';
|
|
1
|
+
export type * from './definitions/interfaces.js';
|
|
2
|
+
export type * from './definitions/types.js';
|
|
3
3
|
export { default as AuthenticationError } from './errors/AuthenticationError.js';
|
|
4
4
|
export { default as NotConnected } from './errors/NotConnected.js';
|
|
5
|
-
export { default as
|
|
6
|
-
export { default } from './
|
|
5
|
+
export { default as GoogleDriver } from './drivers/Google.js';
|
|
6
|
+
export { default as OpenIDDriver } from './drivers/OpenID.js';
|
|
7
|
+
export { default } from './IdentityProvider.js';
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
export * from './definitions/interfaces.js';
|
|
2
|
-
export * from './definitions/types.js';
|
|
3
1
|
export { default as AuthenticationError } from './errors/AuthenticationError.js';
|
|
4
2
|
export { default as NotConnected } from './errors/NotConnected.js';
|
|
5
|
-
export { default as
|
|
6
|
-
export { default } from './
|
|
3
|
+
export { default as GoogleDriver } from './drivers/Google.js';
|
|
4
|
+
export { default as OpenIDDriver } from './drivers/OpenID.js';
|
|
5
|
+
export { default } from './IdentityProvider.js';
|
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@theshelf/authentication",
|
|
3
3
|
"private": false,
|
|
4
|
-
"version": "0.1
|
|
4
|
+
"version": "0.2.1",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"repository": {
|
|
7
|
-
"url": "https://github.com/MaskingTechnology/theshelf"
|
|
7
|
+
"url": "git+https://github.com/MaskingTechnology/theshelf.git"
|
|
8
8
|
},
|
|
9
9
|
"scripts": {
|
|
10
10
|
"build": "tsc",
|
package/dist/implementation.d.ts
DELETED
package/dist/implementation.js
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import UnknownImplementation from './errors/UnknownImplementation.js';
|
|
2
|
-
import createGoogle from './implementations/google/create.js';
|
|
3
|
-
import createOpenID from './implementations/openid/create.js';
|
|
4
|
-
const implementations = new Map([
|
|
5
|
-
['openid', createOpenID],
|
|
6
|
-
['google', createGoogle]
|
|
7
|
-
]);
|
|
8
|
-
const DEFAULT_AUTHENTICATION_IMPLEMENTATION = 'openid';
|
|
9
|
-
const implementationName = process.env.AUTHENTICATION_IMPLEMENTATION ?? DEFAULT_AUTHENTICATION_IMPLEMENTATION;
|
|
10
|
-
const creator = implementations.get(implementationName.toLowerCase());
|
|
11
|
-
if (creator === undefined) {
|
|
12
|
-
throw new UnknownImplementation(implementationName);
|
|
13
|
-
}
|
|
14
|
-
export default creator();
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import Google from './Google.js';
|
|
2
|
-
export default function create() {
|
|
3
|
-
const issuer = process.env.GOOGLE_ISSUER ?? 'undefined';
|
|
4
|
-
const clientId = process.env.GOOGLE_CLIENT_ID ?? 'undefined';
|
|
5
|
-
const clientSecret = process.env.GOOGLE_CLIENT_SECRET ?? 'undefined';
|
|
6
|
-
const redirectPath = process.env.GOOGLE_REDIRECT_PATH ?? 'undefined';
|
|
7
|
-
const accessType = process.env.GOOGLE_ACCESS_TYPE ?? 'online';
|
|
8
|
-
const organizationDomain = process.env.GOOGLE_ORGANIZATION_DOMAIN ?? '';
|
|
9
|
-
return new Google({ issuer, clientId, clientSecret, redirectPath, accessType, organizationDomain });
|
|
10
|
-
}
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import OpenID from './OpenID.js';
|
|
2
|
-
export default function create() {
|
|
3
|
-
const issuer = process.env.OPENID_ISSUER ?? 'undefined';
|
|
4
|
-
const clientId = process.env.OPENID_CLIENT_ID ?? 'undefined';
|
|
5
|
-
const clientSecret = process.env.OPENID_CLIENT_SECRET ?? 'undefined';
|
|
6
|
-
const redirectPath = process.env.OPENID_REDIRECT_PATH ?? 'undefined';
|
|
7
|
-
const allowInsecureRequests = process.env.OPENID_ALLOW_INSECURE_REQUESTS === 'true';
|
|
8
|
-
return new OpenID({ issuer, clientId, clientSecret, redirectPath, allowInsecureRequests });
|
|
9
|
-
}
|