@toa.io/extensions.exposition 0.21.0-alpha.0 → 0.22.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/components/identity.basic/source/authenticate.ts +8 -6
- package/components/identity.basic/source/create.ts +1 -2
- package/components/identity.basic/source/transit.ts +9 -6
- package/components/identity.basic/source/types.ts +2 -2
- package/components/identity.tokens/source/authenticate.ts +6 -5
- package/components/identity.tokens/source/decrypt.ts +12 -4
- package/components/identity.tokens/source/encrypt.test.ts +1 -1
- package/components/identity.tokens/source/types.ts +2 -2
- package/package.json +7 -10
- package/source/Gateway.ts +3 -3
- package/source/directives/auth/Family.ts +1 -2
- package/source/directives/auth/Incept.ts +3 -3
- package/source/directives/auth/types.ts +2 -2
|
@@ -1,27 +1,29 @@
|
|
|
1
1
|
import { atob } from 'buffer'
|
|
2
2
|
import { compare } from 'bcryptjs'
|
|
3
|
-
import { type Query } from '@toa.io/types'
|
|
4
|
-
import { Nope, type Nopeable } from 'nopeable'
|
|
3
|
+
import { type Query, type Maybe } from '@toa.io/types'
|
|
5
4
|
import { type Context } from './types'
|
|
6
5
|
|
|
7
|
-
export async function computation (input: string, context: Context): Promise<
|
|
6
|
+
export async function computation (input: string, context: Context): Promise<Maybe<Output>> {
|
|
8
7
|
const [username, password] = atob(input).split(':')
|
|
9
8
|
const query: Query = { criteria: `username==${username}` }
|
|
10
9
|
const credentials = await context.local.observe({ query })
|
|
11
10
|
|
|
12
|
-
if (credentials instanceof
|
|
11
|
+
if (credentials instanceof Error)
|
|
13
12
|
return credentials
|
|
14
13
|
|
|
15
14
|
if (credentials === null)
|
|
16
|
-
return
|
|
15
|
+
return ERR_NOT_FOUND
|
|
17
16
|
|
|
18
17
|
const spicy = password + context.configuration.pepper
|
|
19
18
|
const match = await compare(spicy, credentials.password)
|
|
20
19
|
|
|
21
20
|
if (match) return { identity: { id: credentials.id } }
|
|
22
|
-
else return
|
|
21
|
+
else return ERR_PASSWORD_MISMATCH
|
|
23
22
|
}
|
|
24
23
|
|
|
24
|
+
const ERR_NOT_FOUND = new Error('NOT_FOUND')
|
|
25
|
+
const ERR_PASSWORD_MISMATCH = new Error('PASSWORD_MISMATCH')
|
|
26
|
+
|
|
25
27
|
interface Output {
|
|
26
28
|
identity: {
|
|
27
29
|
id: string
|
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
import { type Nopeable } from 'nopeable'
|
|
2
1
|
import { type Context } from './types'
|
|
3
2
|
|
|
4
3
|
export async function effect
|
|
5
|
-
(input: CreateInput, context: Context): Promise<
|
|
4
|
+
(input: CreateInput, context: Context): Promise<CreateOutput> {
|
|
6
5
|
const [username, password] = atob(input.credentials).split(':')
|
|
7
6
|
const request = { input: { username, password }, query: { id: input.id } }
|
|
8
7
|
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { genSalt, hash } from 'bcryptjs'
|
|
2
|
-
import { type Operation } from '@toa.io/types'
|
|
3
|
-
import { Nope, type Nopeable } from 'nopeable'
|
|
2
|
+
import { type Maybe, type Operation } from '@toa.io/types'
|
|
4
3
|
import { type Context, type Entity, type TransitInput, type TransitOutput } from './types'
|
|
5
4
|
|
|
6
5
|
export class Transition implements Operation {
|
|
@@ -21,7 +20,7 @@ export class Transition implements Operation {
|
|
|
21
20
|
this.passwrodRx = toRx(context.configuration.password)
|
|
22
21
|
}
|
|
23
22
|
|
|
24
|
-
public async execute (input: TransitInput, object: Entity): Promise<
|
|
23
|
+
public async execute (input: TransitInput, object: Entity): Promise<Maybe<TransitOutput>> {
|
|
25
24
|
const existent = object._version !== 0
|
|
26
25
|
|
|
27
26
|
if (existent)
|
|
@@ -29,17 +28,17 @@ export class Transition implements Operation {
|
|
|
29
28
|
|
|
30
29
|
if (input.username !== undefined) {
|
|
31
30
|
if (existent && object.username === this.principal)
|
|
32
|
-
return
|
|
31
|
+
return ERR_PRINCIPAL_LOCKED
|
|
33
32
|
|
|
34
33
|
if (invalid(input.username, this.usernameRx))
|
|
35
|
-
return
|
|
34
|
+
return ERR_INVALID_USERNAME
|
|
36
35
|
|
|
37
36
|
object.username = input.username
|
|
38
37
|
}
|
|
39
38
|
|
|
40
39
|
if (input.password !== undefined) {
|
|
41
40
|
if (invalid(input.password, this.passwrodRx))
|
|
42
|
-
return
|
|
41
|
+
return ERR_INVALID_PASSWORD
|
|
43
42
|
|
|
44
43
|
const salt = await genSalt(this.rounds)
|
|
45
44
|
const spicy = input.password + this.pepper
|
|
@@ -61,4 +60,8 @@ function invalid (value: string, expressions: RegExp[]): boolean {
|
|
|
61
60
|
return expressions.some((expression) => !expression.test(value))
|
|
62
61
|
}
|
|
63
62
|
|
|
63
|
+
const ERR_PRINCIPAL_LOCKED = new Error('PRINCIPAL_LOCKED')
|
|
64
|
+
const ERR_INVALID_USERNAME = new Error('INVALID_USERNAME')
|
|
65
|
+
const ERR_INVALID_PASSWORD = new Error('INVALID_PASSWORD')
|
|
66
|
+
|
|
64
67
|
type Tokens = Context['remote']['identity']['tokens']
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { type Call, type Observation, type Query } from '@toa.io/types'
|
|
1
|
+
import { type Call, type Maybe, type Observation, type Query } from '@toa.io/types'
|
|
2
2
|
|
|
3
3
|
export interface Context {
|
|
4
4
|
local: {
|
|
5
|
-
observe: Observation<Entity
|
|
5
|
+
observe: Observation<Maybe<Entity>>
|
|
6
6
|
transit: Call<TransitOutput, TransitInput>
|
|
7
7
|
}
|
|
8
8
|
remote: {
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { type Operation } from '@toa.io/types'
|
|
1
|
+
import { type Maybe, type Operation } from '@toa.io/types'
|
|
3
2
|
import { type AuthenticateOutput, type Context } from './types'
|
|
4
3
|
|
|
5
4
|
export class Computation implements Operation {
|
|
@@ -13,10 +12,10 @@ export class Computation implements Operation {
|
|
|
13
12
|
this.observe = context.local.observe
|
|
14
13
|
}
|
|
15
14
|
|
|
16
|
-
public async execute (token: string): Promise<
|
|
15
|
+
public async execute (token: string): Promise<Maybe<AuthenticateOutput>> {
|
|
17
16
|
const claim = await this.decrypt({ input: token })
|
|
18
17
|
|
|
19
|
-
if (claim instanceof
|
|
18
|
+
if (claim instanceof Error)
|
|
20
19
|
return claim
|
|
21
20
|
|
|
22
21
|
const identity = claim.identity
|
|
@@ -28,7 +27,7 @@ export class Computation implements Operation {
|
|
|
28
27
|
const revocation = await this.observe({ query: { id: identity.id } })
|
|
29
28
|
|
|
30
29
|
if (revocation !== null && iat < revocation.revokedAt)
|
|
31
|
-
return
|
|
30
|
+
return ERR_TOKEN_REVOKED
|
|
32
31
|
}
|
|
33
32
|
|
|
34
33
|
const refresh = stale || claim.refresh
|
|
@@ -36,3 +35,5 @@ export class Computation implements Operation {
|
|
|
36
35
|
return { identity, refresh }
|
|
37
36
|
}
|
|
38
37
|
}
|
|
38
|
+
|
|
39
|
+
const ERR_TOKEN_REVOKED = new Error('TOKEN_REVOKED')
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { V3 } from 'paseto'
|
|
2
|
-
import {
|
|
2
|
+
import { type Maybe } from '@toa.io/types'
|
|
3
3
|
import { type Context, type Claim, type DecryptOutput } from './types'
|
|
4
4
|
|
|
5
5
|
export async function computation (token: string, context: Context):
|
|
6
|
-
Promise<
|
|
6
|
+
Promise<Maybe<DecryptOutput>> {
|
|
7
7
|
let refresh = false
|
|
8
8
|
let claim = await decrypt(token, context.configuration.key0)
|
|
9
9
|
|
|
@@ -12,8 +12,14 @@ Promise<Nopeable<DecryptOutput>> {
|
|
|
12
12
|
claim = await decrypt(token, context.configuration.key1)
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
if (claim === null)
|
|
16
|
-
|
|
15
|
+
if (claim === null)
|
|
16
|
+
return ERR_INVALID_TOKEN
|
|
17
|
+
else return {
|
|
18
|
+
identity: claim.identity,
|
|
19
|
+
iat: claim.iat,
|
|
20
|
+
exp: claim.exp,
|
|
21
|
+
refresh
|
|
22
|
+
}
|
|
17
23
|
}
|
|
18
24
|
|
|
19
25
|
async function decrypt (token: string, key: string): Promise<Claim | null> {
|
|
@@ -23,3 +29,5 @@ async function decrypt (token: string, key: string): Promise<Claim | null> {
|
|
|
23
29
|
return null
|
|
24
30
|
}
|
|
25
31
|
}
|
|
32
|
+
|
|
33
|
+
const ERR_INVALID_TOKEN = new Error('INVALID_TOKEN')
|
|
@@ -31,5 +31,5 @@ it('should encrypt with given lifetime', async () => {
|
|
|
31
31
|
|
|
32
32
|
await timeout(lifetime * 1000)
|
|
33
33
|
|
|
34
|
-
await expect(decrypt(encrypted, context)).resolves.toMatchObject({
|
|
34
|
+
await expect(decrypt(encrypted, context)).resolves.toMatchObject({ message: 'INVALID_TOKEN' })
|
|
35
35
|
})
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { type Call, type Observation } from '@toa.io/types'
|
|
1
|
+
import { type Call, type Maybe, type Observation } from '@toa.io/types'
|
|
2
2
|
|
|
3
3
|
export interface Context {
|
|
4
4
|
local: {
|
|
5
5
|
observe: Observation<Entity>
|
|
6
|
-
decrypt: Call<DecryptOutput
|
|
6
|
+
decrypt: Call<Maybe<DecryptOutput>, string>
|
|
7
7
|
}
|
|
8
8
|
configuration: Configuration
|
|
9
9
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@toa.io/extensions.exposition",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.22.1",
|
|
4
4
|
"description": "Toa Exposition",
|
|
5
5
|
"author": "temich <tema.gurtovoy@gmail.com>",
|
|
6
6
|
"homepage": "https://github.com/toa-io/toa#readme",
|
|
@@ -16,15 +16,12 @@
|
|
|
16
16
|
"publishConfig": {
|
|
17
17
|
"access": "public"
|
|
18
18
|
},
|
|
19
|
-
"peerDependencies": {
|
|
20
|
-
"nopeable": "*"
|
|
21
|
-
},
|
|
22
19
|
"dependencies": {
|
|
23
|
-
"@toa.io/core": "0.
|
|
24
|
-
"@toa.io/generic": "0.
|
|
25
|
-
"@toa.io/http": "0.
|
|
26
|
-
"@toa.io/schemas": "0.
|
|
27
|
-
"@toa.io/streams": "0.
|
|
20
|
+
"@toa.io/core": "0.22.1",
|
|
21
|
+
"@toa.io/generic": "0.22.0",
|
|
22
|
+
"@toa.io/http": "0.22.0",
|
|
23
|
+
"@toa.io/schemas": "0.22.0",
|
|
24
|
+
"@toa.io/streams": "0.22.0",
|
|
28
25
|
"bcryptjs": "2.4.3",
|
|
29
26
|
"cors": "2.8.5",
|
|
30
27
|
"express": "4.18.2",
|
|
@@ -49,5 +46,5 @@
|
|
|
49
46
|
"@types/express": "4.17.17",
|
|
50
47
|
"@types/negotiator": "0.6.1"
|
|
51
48
|
},
|
|
52
|
-
"gitHead": "
|
|
49
|
+
"gitHead": "d1116e1a3c3d881d2204989851596f0715abcb0f"
|
|
53
50
|
}
|
package/source/Gateway.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type bindings, Connector } from '@toa.io/core'
|
|
2
|
-
import {
|
|
2
|
+
import { type Maybe } from '@toa.io/types'
|
|
3
3
|
import * as http from './HTTP'
|
|
4
4
|
import { rethrow } from './exceptions'
|
|
5
5
|
import { type Method, type Parameter, type Tree } from './RTD'
|
|
@@ -65,9 +65,9 @@ export class Gateway extends Connector {
|
|
|
65
65
|
|
|
66
66
|
const reply = await method.endpoint
|
|
67
67
|
.call(request.body, request.query, parameters)
|
|
68
|
-
.catch(rethrow) as
|
|
68
|
+
.catch(rethrow) as Maybe<unknown>
|
|
69
69
|
|
|
70
|
-
if (reply instanceof
|
|
70
|
+
if (reply instanceof Error)
|
|
71
71
|
throw new http.Conflict(reply)
|
|
72
72
|
|
|
73
73
|
return { body: reply }
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { type Component } from '@toa.io/core'
|
|
2
|
-
import { Nope } from 'nopeable'
|
|
3
2
|
import { type Parameter } from '../../RTD'
|
|
4
3
|
import { type Family, type Output } from '../../Directive'
|
|
5
4
|
import { type Remotes } from '../../Remotes'
|
|
@@ -107,7 +106,7 @@ class Authorization implements Family<Directive, Extension> {
|
|
|
107
106
|
const result = await this.schemes[scheme]
|
|
108
107
|
.invoke<AuthenticationResult>('authenticate', { input: credentials })
|
|
109
108
|
|
|
110
|
-
if (result instanceof
|
|
109
|
+
if (result instanceof Error)
|
|
111
110
|
return null
|
|
112
111
|
|
|
113
112
|
const identity = result.identity
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { type Maybe } from '@toa.io/types'
|
|
2
2
|
import * as http from '../../HTTP'
|
|
3
3
|
import { type Directive, type Discovery, type Identity, type Input, type Schemes } from './types'
|
|
4
4
|
import { split } from './split'
|
|
@@ -31,9 +31,9 @@ export class Incept implements Directive {
|
|
|
31
31
|
this.schemes[scheme] ??= await this.discovery[provider]
|
|
32
32
|
|
|
33
33
|
const identity = await this.schemes[scheme]
|
|
34
|
-
.invoke<
|
|
34
|
+
.invoke<Maybe<Identity>>('create', { input: { id, credentials } })
|
|
35
35
|
|
|
36
|
-
if (identity instanceof
|
|
36
|
+
if (identity instanceof Error)
|
|
37
37
|
throw new http.Conflict(identity)
|
|
38
38
|
|
|
39
39
|
request.identity = identity
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type Component } from '@toa.io/core'
|
|
2
|
-
import { type
|
|
2
|
+
import { type Maybe } from '@toa.io/types'
|
|
3
3
|
import { type Parameter } from '../../RTD'
|
|
4
4
|
import type * as http from '../../HTTP'
|
|
5
5
|
import type * as directive from '../../Directive'
|
|
@@ -29,7 +29,7 @@ export interface Ban {
|
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
export type Input = directive.Input & Extension
|
|
32
|
-
export type AuthenticationResult =
|
|
32
|
+
export type AuthenticationResult = Maybe<{ identity: Identity, refresh: boolean }>
|
|
33
33
|
|
|
34
34
|
export type Scheme = 'basic' | 'token'
|
|
35
35
|
export type Remote = 'basic' | 'tokens' | 'roles' | 'bans'
|