@owlmeans/server-oidc-rp 0.1.2 → 0.1.4
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 +59 -474
- package/build/actions/init.d.ts +2 -2
- package/build/actions/init.d.ts.map +1 -1
- package/build/actions/init.js +2 -2
- package/build/actions/init.js.map +1 -1
- package/build/actions/process.d.ts +2 -2
- package/build/actions/process.d.ts.map +1 -1
- package/build/actions/process.js.map +1 -1
- package/build/auth/plugins/google-client.d.ts +4 -0
- package/build/auth/plugins/google-client.d.ts.map +1 -0
- package/build/auth/plugins/google-client.js +147 -0
- package/build/auth/plugins/google-client.js.map +1 -0
- package/build/auth/plugins/index.d.ts.map +1 -1
- package/build/auth/plugins/index.js +3 -1
- package/build/auth/plugins/index.js.map +1 -1
- package/build/gate.d.ts +1 -1
- package/build/gate.d.ts.map +1 -1
- package/build/guard.d.ts +2 -2
- package/build/guard.d.ts.map +1 -1
- package/build/guard.js +1 -1
- package/build/guard.js.map +1 -1
- package/build/index.d.ts +1 -1
- package/build/index.d.ts.map +1 -1
- package/build/index.js.map +1 -1
- package/build/model/gate.js +1 -1
- package/build/model/gate.js.map +1 -1
- package/build/modules.d.ts +2 -2
- package/build/modules.d.ts.map +1 -1
- package/build/modules.js +3 -3
- package/build/modules.js.map +1 -1
- package/build/service.d.ts.map +1 -1
- package/build/service.js +15 -7
- package/build/service.js.map +1 -1
- package/build/types.d.ts +44 -7
- package/build/types.d.ts.map +1 -1
- package/build/utils/auth.d.ts +2 -2
- package/build/utils/auth.d.ts.map +1 -1
- package/build/utils/auth.js.map +1 -1
- package/build/utils/types.d.ts +3 -2
- package/build/utils/types.d.ts.map +1 -1
- package/build/wrapper.js +2 -2
- package/build/wrapper.js.map +1 -1
- package/package.json +24 -20
- package/src/actions/init.ts +5 -5
- package/src/actions/process.ts +2 -2
- package/src/auth/plugins/google-client.ts +189 -0
- package/src/auth/plugins/index.ts +3 -1
- package/src/gate.ts +1 -1
- package/src/guard.ts +3 -3
- package/src/index.ts +17 -1
- package/src/model/gate.ts +2 -2
- package/src/modules.ts +6 -6
- package/src/service.ts +22 -12
- package/src/types.ts +67 -8
- package/src/utils/auth.ts +2 -3
- package/src/utils/types.ts +3 -2
- package/src/wrapper.ts +8 -8
- package/tests/context.ts +44 -0
- package/tests/google-client.spec.ts +123 -0
- package/tests/service.spec.ts +75 -0
- package/tests/tsconfig.json +12 -0
- package/tsconfig.json +6 -5
- package/build/.gitkeep +0 -0
package/src/utils/auth.ts
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import type { AuthCredentials } from '@owlmeans/auth'
|
|
2
2
|
import { AuthenFailed, AuthenPayloadError } from '@owlmeans/auth'
|
|
3
|
-
import type { Config, Context, OidcClientService,
|
|
3
|
+
import type { Config, Context, OidcClientService, OidcTokenSet } from '../types.js'
|
|
4
4
|
import type { OidcProviderConfig } from '@owlmeans/oidc'
|
|
5
|
-
import type { } from 'openid-client'
|
|
6
5
|
import Url from 'url'
|
|
7
6
|
import { cache, exchangeId, verifierId } from './cache.js'
|
|
8
7
|
import { DEFAULT_ALIAS } from '../consts.js'
|
|
@@ -11,7 +10,7 @@ import { randomBytes } from '@noble/hashes/utils'
|
|
|
11
10
|
import { AUTHEN_TIMEFRAME } from '@owlmeans/server-auth'
|
|
12
11
|
|
|
13
12
|
export const makeOidcAuthentication = <C extends Config, T extends Context<C>>(context: T) =>
|
|
14
|
-
async (credential: AuthCredentials): Promise<[OidcProviderConfig,
|
|
13
|
+
async (credential: AuthCredentials): Promise<[OidcProviderConfig, OidcTokenSet, string]> => {
|
|
15
14
|
console.log('Challenge we got: ', credential.challenge)
|
|
16
15
|
const challengeParts = credential.challenge.split(':http')
|
|
17
16
|
// This is ex. source - url we were going to return user to after all it happens
|
package/src/utils/types.ts
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import type { AuthSpent } from '@owlmeans/server-auth'
|
|
2
|
-
import type {
|
|
2
|
+
import type { OidcTokenSetParameters } from '../types.js'
|
|
3
3
|
|
|
4
4
|
export interface OIDCAuthCache extends AuthSpent {
|
|
5
5
|
verifier?: string
|
|
6
|
-
payload?:
|
|
6
|
+
payload?: OidcTokenSetParameters
|
|
7
7
|
client?: string
|
|
8
8
|
validated?: Date
|
|
9
9
|
entityId?: string
|
|
10
|
+
redirectUri?: string
|
|
10
11
|
}
|
package/src/wrapper.ts
CHANGED
|
@@ -5,10 +5,10 @@ import { createService } from '@owlmeans/context'
|
|
|
5
5
|
import type { CommonTokenSetParams, OIDCTokenUpdate, WrappedOIDCService } from '@owlmeans/oidc'
|
|
6
6
|
import { OIDC_WRAPPED_TOKEN, WRAPPED_OIDC } from '@owlmeans/oidc'
|
|
7
7
|
import { cache, managedId } from './utils/cache.js'
|
|
8
|
-
import type { Config, Context, OidcClientService,
|
|
8
|
+
import type { Config, Context, OidcClientService, OidcTokenSetParameters } from './types.js'
|
|
9
9
|
import { authService, DEFAULT_ALIAS, OIDC_AUTH_LIFTETIME, OIDC_WRAP_FRESHNESS } from './consts.js'
|
|
10
10
|
import days from 'dayjs'
|
|
11
|
-
import type {
|
|
11
|
+
import type { ClientEntrypoint } from '@owlmeans/client-entrypoint'
|
|
12
12
|
import { TRUSTED } from '@owlmeans/config'
|
|
13
13
|
import { AUTH_SRV_KEY } from '@owlmeans/server-auth'
|
|
14
14
|
import { trust } from '@owlmeans/auth-common/utils'
|
|
@@ -38,8 +38,8 @@ export const makeOidcWrappingService = (): WrappedOIDCService => {
|
|
|
38
38
|
|
|
39
39
|
record.validated = new Date()
|
|
40
40
|
|
|
41
|
-
if (ctx.
|
|
42
|
-
const [update] = await ctx.
|
|
41
|
+
if (ctx.hasEntrypoint(authService.auth.update)) {
|
|
42
|
+
const [update] = await ctx.entrypoint<ClientEntrypoint<OIDCTokenUpdate>>(authService.auth.update)
|
|
43
43
|
.call({ body: { token, tokenSet: record.payload } })
|
|
44
44
|
|
|
45
45
|
const updateEnvelope = makeEnvelopeModel<AuthCredentials>(update.token, EnvelopeKind.Token)
|
|
@@ -70,7 +70,7 @@ export const makeOidcWrappingService = (): WrappedOIDCService => {
|
|
|
70
70
|
const authorization = await makeEnvelopeModel<Auth>(OIDC_WRAPPED_TOKEN)
|
|
71
71
|
.send(updatedUser, null).sign(trusted.key, EnvelopeKind.Token)
|
|
72
72
|
|
|
73
|
-
record.payload = update.tokenSet as
|
|
73
|
+
record.payload = update.tokenSet as OidcTokenSetParameters
|
|
74
74
|
|
|
75
75
|
await cache(ctx).save(record, { ttl: OIDC_AUTH_LIFTETIME / 1000 })
|
|
76
76
|
|
|
@@ -82,9 +82,9 @@ export const makeOidcWrappingService = (): WrappedOIDCService => {
|
|
|
82
82
|
let tokenSet: CommonTokenSetParams = record.payload as CommonTokenSetParams
|
|
83
83
|
const client = await oidc.getClient(defaultClientId)
|
|
84
84
|
if (days.unix(tokenSet.expires_at ?? 0).isBefore()) {
|
|
85
|
-
tokenSet = await client.refresh(tokenSet as
|
|
85
|
+
tokenSet = await client.refresh(tokenSet as OidcTokenSetParameters) as CommonTokenSetParams
|
|
86
86
|
} else {
|
|
87
|
-
const result = await client.introspect(tokenSet as
|
|
87
|
+
const result = await client.introspect(tokenSet as OidcTokenSetParameters, 'access_token')
|
|
88
88
|
if (!result.active) {
|
|
89
89
|
throw new AuthorizationError('access-token')
|
|
90
90
|
}
|
|
@@ -99,7 +99,7 @@ export const makeOidcWrappingService = (): WrappedOIDCService => {
|
|
|
99
99
|
const authorization = await makeEnvelopeModel<Auth>(OIDC_WRAPPED_TOKEN)
|
|
100
100
|
.send(updatedUser, null).sign(trusted.key, EnvelopeKind.Token)
|
|
101
101
|
|
|
102
|
-
record.payload = tokenSet as
|
|
102
|
+
record.payload = tokenSet as OidcTokenSetParameters
|
|
103
103
|
|
|
104
104
|
await cache(ctx).save(record, { ttl: OIDC_AUTH_LIFTETIME / 1000 })
|
|
105
105
|
|
package/tests/context.ts
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { AppType, Layer, makeBasicContext } from '@owlmeans/context'
|
|
2
|
+
import type { BasicConfig, BasicContext } from '@owlmeans/context'
|
|
3
|
+
import { createStaticResource } from '@owlmeans/static-resource'
|
|
4
|
+
import { AUTH_CACHE } from '@owlmeans/server-auth'
|
|
5
|
+
import { makeOidcClientService } from '../src/service.js'
|
|
6
|
+
import { GOOGLE_SERVICE } from '@owlmeans/oidc'
|
|
7
|
+
import type { Config } from '../src/types.js'
|
|
8
|
+
|
|
9
|
+
const RESOURCE_KEY = 'server-oidc-rp-test-cache'
|
|
10
|
+
|
|
11
|
+
export const makeTestContext = () => {
|
|
12
|
+
const cfg: Config = {
|
|
13
|
+
ready: false,
|
|
14
|
+
service: 'server-oidc-rp-tests',
|
|
15
|
+
layer: Layer.Service,
|
|
16
|
+
type: AppType.Backend,
|
|
17
|
+
services: {},
|
|
18
|
+
oidc: {
|
|
19
|
+
providers: [
|
|
20
|
+
{
|
|
21
|
+
clientId: 'google-client-id-123',
|
|
22
|
+
secret: 'google-secret-456',
|
|
23
|
+
service: GOOGLE_SERVICE,
|
|
24
|
+
basePath: 'unused-in-unit-tests',
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
clientId: 'keycloak-admin',
|
|
28
|
+
secret: 'kc-secret',
|
|
29
|
+
service: 'iam-product-api',
|
|
30
|
+
basePath: 'realms/master',
|
|
31
|
+
def: true,
|
|
32
|
+
internal: true,
|
|
33
|
+
},
|
|
34
|
+
],
|
|
35
|
+
},
|
|
36
|
+
} as Config
|
|
37
|
+
|
|
38
|
+
const context = makeBasicContext(cfg) as BasicContext<Config>
|
|
39
|
+
|
|
40
|
+
context.registerResource(createStaticResource(AUTH_CACHE, RESOURCE_KEY))
|
|
41
|
+
context.registerService(makeOidcClientService())
|
|
42
|
+
|
|
43
|
+
return context
|
|
44
|
+
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { describe, test, expect } from 'bun:test'
|
|
2
|
+
import { makeTestContext } from './context.js'
|
|
3
|
+
import { googleClientPlugin } from '../src/auth/plugins/google-client.js'
|
|
4
|
+
import { GOOGLE_SERVICE } from '@owlmeans/oidc'
|
|
5
|
+
import { AuthenPayloadError, AuthRole } from '@owlmeans/auth'
|
|
6
|
+
import { UnknownRecordError } from '@owlmeans/resource'
|
|
7
|
+
import { verifierId } from '../src/utils/cache.js'
|
|
8
|
+
import { AUTH_CACHE } from '@owlmeans/server-auth'
|
|
9
|
+
import type { Resource } from '@owlmeans/resource'
|
|
10
|
+
import type { OIDCAuthCache } from '../src/utils/types.js'
|
|
11
|
+
import type { Config, Context } from '../src/types.js'
|
|
12
|
+
import type { BasicContext } from '@owlmeans/context'
|
|
13
|
+
|
|
14
|
+
const initContext = async () => {
|
|
15
|
+
const ctx = makeTestContext()
|
|
16
|
+
ctx.configure()
|
|
17
|
+
await ctx.init()
|
|
18
|
+
return ctx
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
describe('@owlmeans/server-oidc-rp — googleClientPlugin.init', () => {
|
|
22
|
+
test('rejects when request.source is missing', async () => {
|
|
23
|
+
const ctx = await initContext()
|
|
24
|
+
const plugin = googleClientPlugin(ctx as unknown as Context, GOOGLE_SERVICE)
|
|
25
|
+
|
|
26
|
+
await expect(
|
|
27
|
+
plugin.init({ type: 'google-oauth', source: undefined as any })
|
|
28
|
+
).rejects.toBeInstanceOf(AuthenPayloadError)
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
test('returns a Google auth URL with correct params', async () => {
|
|
32
|
+
const ctx = await initContext()
|
|
33
|
+
const plugin = googleClientPlugin(ctx as unknown as Context, GOOGLE_SERVICE)
|
|
34
|
+
|
|
35
|
+
const result = await plugin.init({
|
|
36
|
+
type: 'google-oauth',
|
|
37
|
+
source: 'https://example.com/authentication/login/google-oauth',
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
expect(result.challenge).toContain('https://accounts.google.com/o/oauth2/v2/auth')
|
|
41
|
+
|
|
42
|
+
const url = new URL(result.challenge)
|
|
43
|
+
expect(url.searchParams.get('client_id')).toBe('google-client-id-123')
|
|
44
|
+
expect(url.searchParams.get('redirect_uri')).toBe('https://example.com/authentication/login/google-oauth')
|
|
45
|
+
expect(url.searchParams.get('response_type')).toBe('code')
|
|
46
|
+
expect(url.searchParams.get('code_challenge_method')).toBe('S256')
|
|
47
|
+
expect(url.searchParams.get('state')).not.toBeNull()
|
|
48
|
+
expect(url.searchParams.get('state')!.length).toBeGreaterThan(0)
|
|
49
|
+
expect(url.searchParams.get('code_challenge')).not.toBeNull()
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
test('caches PKCE verifier under verifierId(state) with redirectUri', async () => {
|
|
53
|
+
const ctx = await initContext()
|
|
54
|
+
const plugin = googleClientPlugin(ctx as unknown as Context, GOOGLE_SERVICE)
|
|
55
|
+
|
|
56
|
+
const result = await plugin.init({
|
|
57
|
+
type: 'google-oauth',
|
|
58
|
+
source: 'https://example.com/auth/login/google-oauth',
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
const url = new URL(result.challenge)
|
|
62
|
+
const state = url.searchParams.get('state')!
|
|
63
|
+
|
|
64
|
+
const cache = (ctx as unknown as BasicContext<Config>).resource<Resource<OIDCAuthCache>>(AUTH_CACHE)
|
|
65
|
+
const record = await cache.load(verifierId(state))
|
|
66
|
+
|
|
67
|
+
expect(record).not.toBeNull()
|
|
68
|
+
expect(record!.verifier).toBeDefined()
|
|
69
|
+
expect(record!.verifier!.length).toBeGreaterThan(0)
|
|
70
|
+
expect(record!.client).toBe('google-client-id-123')
|
|
71
|
+
expect(record!.redirectUri).toBe('https://example.com/auth/login/google-oauth')
|
|
72
|
+
})
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
describe('@owlmeans/server-oidc-rp — googleClientPlugin.authenticate', () => {
|
|
76
|
+
test('rejects when code is missing', async () => {
|
|
77
|
+
const ctx = await initContext()
|
|
78
|
+
const plugin = googleClientPlugin(ctx as unknown as Context, GOOGLE_SERVICE)
|
|
79
|
+
|
|
80
|
+
await expect(
|
|
81
|
+
plugin.authenticate({
|
|
82
|
+
type: 'google-oauth',
|
|
83
|
+
challenge: '',
|
|
84
|
+
credential: 'state=abc123',
|
|
85
|
+
role: AuthRole.User,
|
|
86
|
+
userId: 'code',
|
|
87
|
+
scopes: ['*'],
|
|
88
|
+
})
|
|
89
|
+
).rejects.toBeInstanceOf(AuthenPayloadError)
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
test('rejects when state is missing', async () => {
|
|
93
|
+
const ctx = await initContext()
|
|
94
|
+
const plugin = googleClientPlugin(ctx as unknown as Context, GOOGLE_SERVICE)
|
|
95
|
+
|
|
96
|
+
await expect(
|
|
97
|
+
plugin.authenticate({
|
|
98
|
+
type: 'google-oauth',
|
|
99
|
+
challenge: '',
|
|
100
|
+
credential: 'code=abc123',
|
|
101
|
+
role: AuthRole.User,
|
|
102
|
+
userId: 'code',
|
|
103
|
+
scopes: ['*'],
|
|
104
|
+
})
|
|
105
|
+
).rejects.toBeInstanceOf(AuthenPayloadError)
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
test('rejects when verifier not found for state', async () => {
|
|
109
|
+
const ctx = await initContext()
|
|
110
|
+
const plugin = googleClientPlugin(ctx as unknown as Context, GOOGLE_SERVICE)
|
|
111
|
+
|
|
112
|
+
await expect(
|
|
113
|
+
plugin.authenticate({
|
|
114
|
+
type: 'google-oauth',
|
|
115
|
+
challenge: '',
|
|
116
|
+
credential: 'code=abc123&state=nonexistent-state',
|
|
117
|
+
role: AuthRole.User,
|
|
118
|
+
userId: 'code',
|
|
119
|
+
scopes: ['*'],
|
|
120
|
+
})
|
|
121
|
+
).rejects.toBeInstanceOf(UnknownRecordError)
|
|
122
|
+
})
|
|
123
|
+
})
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { describe, test, expect } from 'bun:test'
|
|
2
|
+
import { makeTestContext } from './context.js'
|
|
3
|
+
import { GOOGLE_SERVICE } from '@owlmeans/oidc'
|
|
4
|
+
import { AuthManagerError } from '@owlmeans/auth'
|
|
5
|
+
import type { OidcClientService } from '../src/types.js'
|
|
6
|
+
import { DEFAULT_ALIAS } from '../src/consts.js'
|
|
7
|
+
|
|
8
|
+
describe('@owlmeans/server-oidc-rp — getConfig', () => {
|
|
9
|
+
test('finds provider by service name only', async () => {
|
|
10
|
+
const ctx = makeTestContext()
|
|
11
|
+
ctx.configure()
|
|
12
|
+
await ctx.init()
|
|
13
|
+
|
|
14
|
+
const oidc = ctx.service<OidcClientService>(DEFAULT_ALIAS)
|
|
15
|
+
const cfg = await oidc.getConfig({ service: GOOGLE_SERVICE })
|
|
16
|
+
|
|
17
|
+
expect(cfg).not.toBeNull()
|
|
18
|
+
expect(cfg!.clientId).toBe('google-client-id-123')
|
|
19
|
+
expect(cfg!.secret).toBe('google-secret-456')
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
test('finds provider by clientId string', async () => {
|
|
23
|
+
const ctx = makeTestContext()
|
|
24
|
+
ctx.configure()
|
|
25
|
+
await ctx.init()
|
|
26
|
+
|
|
27
|
+
const oidc = ctx.service<OidcClientService>(DEFAULT_ALIAS)
|
|
28
|
+
const cfg = await oidc.getConfig('keycloak-admin')
|
|
29
|
+
|
|
30
|
+
expect(cfg).not.toBeNull()
|
|
31
|
+
expect(cfg!.service).toBe('iam-product-api')
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
test('throws when lookup has neither clientId nor service', async () => {
|
|
35
|
+
const ctx = makeTestContext()
|
|
36
|
+
ctx.configure()
|
|
37
|
+
await ctx.init()
|
|
38
|
+
|
|
39
|
+
const oidc = ctx.service<OidcClientService>(DEFAULT_ALIAS)
|
|
40
|
+
|
|
41
|
+
await expect(oidc.getConfig({})).rejects.toBeInstanceOf(AuthManagerError)
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
test('returns undefined for unknown service', async () => {
|
|
45
|
+
const ctx = makeTestContext()
|
|
46
|
+
ctx.configure()
|
|
47
|
+
await ctx.init()
|
|
48
|
+
|
|
49
|
+
const oidc = ctx.service<OidcClientService>(DEFAULT_ALIAS)
|
|
50
|
+
const cfg = await oidc.getConfig({ service: 'nonexistent-service' })
|
|
51
|
+
|
|
52
|
+
expect(cfg).toBeUndefined()
|
|
53
|
+
})
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
describe('@owlmeans/server-oidc-rp — getConfiguration guard', () => {
|
|
57
|
+
test('throws oidc.client.client-id when service-only provider has no clientId on provider or param', async () => {
|
|
58
|
+
const ctx = makeTestContext()
|
|
59
|
+
// Add a provider with no clientId
|
|
60
|
+
;(ctx.cfg as any).oidc.providers.push({
|
|
61
|
+
service: 'no-client-id-svc',
|
|
62
|
+
secret: 'some-secret',
|
|
63
|
+
basePath: 'realms/test',
|
|
64
|
+
})
|
|
65
|
+
ctx.configure()
|
|
66
|
+
await ctx.init()
|
|
67
|
+
|
|
68
|
+
const oidc = ctx.service<OidcClientService>(DEFAULT_ALIAS)
|
|
69
|
+
|
|
70
|
+
// getConfiguration should throw because neither param.clientId nor cfg.clientId exist
|
|
71
|
+
await expect(
|
|
72
|
+
oidc.getConfiguration({ service: 'no-client-id-svc' })
|
|
73
|
+
).rejects.toThrow('oidc.client.client-id')
|
|
74
|
+
})
|
|
75
|
+
})
|
package/tsconfig.json
CHANGED
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"extends": [
|
|
3
|
-
"
|
|
3
|
+
"@owlmeans/dep-config/tsconfig.base.json",
|
|
4
|
+
"@owlmeans/dep-config/tsconfig.node.json"
|
|
4
5
|
],
|
|
5
6
|
"compilerOptions": {
|
|
6
|
-
"rootDir": "./src/",
|
|
7
|
-
"outDir": "./build/"
|
|
8
|
-
"moduleResolution": "Bundler",
|
|
7
|
+
"rootDir": "./src/",
|
|
8
|
+
"outDir": "./build/"
|
|
9
9
|
},
|
|
10
10
|
"exclude": [
|
|
11
11
|
"./dist/**/*",
|
|
12
12
|
"./build/**/*",
|
|
13
|
+
"./tests/**/*",
|
|
13
14
|
"./*.ts"
|
|
14
15
|
]
|
|
15
|
-
}
|
|
16
|
+
}
|
package/build/.gitkeep
DELETED
|
File without changes
|