@things-factory/oauth2-client 8.0.0-beta.9 → 8.0.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@things-factory/oauth2-client",
3
- "version": "8.0.0-beta.9",
3
+ "version": "8.0.2",
4
4
  "main": "dist-server/index.js",
5
5
  "browser": "dist-client/index.js",
6
6
  "things-factory": true,
@@ -27,15 +27,15 @@
27
27
  "migration:create": "node ../../node_modules/typeorm/cli.js migration:create ./server/migrations/migration"
28
28
  },
29
29
  "dependencies": {
30
- "@operato/graphql": "^8.0.0-beta",
31
- "@operato/help": "^8.0.0-beta",
32
- "@operato/layout": "^8.0.0-beta",
33
- "@operato/shell": "^8.0.0-beta",
34
- "@operato/utils": "^8.0.0-beta",
35
- "@things-factory/auth-base": "^8.0.0-beta.9",
36
- "@things-factory/shell": "^8.0.0-beta.9",
30
+ "@operato/graphql": "^8.0.0",
31
+ "@operato/help": "^8.0.0",
32
+ "@operato/layout": "^8.0.0",
33
+ "@operato/shell": "^8.0.0",
34
+ "@operato/utils": "^8.0.0",
35
+ "@things-factory/auth-base": "^8.0.2",
36
+ "@things-factory/shell": "^8.0.2",
37
37
  "client-oauth2": "^4.3.3",
38
38
  "clipboard": "^2.0.6"
39
39
  },
40
- "gitHead": "86b1dfa26292926a2d5447fdc23bfa5a9e983245"
40
+ "gitHead": "39d60f56e142561233ddf6d47b539c637971357c"
41
41
  }
@@ -0,0 +1,3 @@
1
+ import './routes'
2
+
3
+ export * from './service'
@@ -0,0 +1,156 @@
1
+ import ClientOAuth2 from 'client-oauth2'
2
+ import crypto from 'crypto'
3
+
4
+ import { getRedirectSubdomainPath, getRepository } from '@things-factory/shell'
5
+
6
+ import { Oauth2Client } from './service/oauth2-client/oauth2-client'
7
+
8
+ process.on('bootstrap-module-domain-private-route' as any, (app, domainPrivateRouter) => {
9
+ domainPrivateRouter.get('/oauth2-client/:id/auth-uri', async (context, next) => {
10
+ const { params, origin } = context
11
+ const { id } = params
12
+
13
+ const repository = getRepository(Oauth2Client)
14
+ const oauth2Client = await repository.findOneBy({ id })
15
+
16
+ const {
17
+ grantType,
18
+ clientId,
19
+ clientSecret,
20
+ accessTokenUrl: accessTokenUri,
21
+ authUrl: authorizationUri,
22
+ scopes
23
+ } = oauth2Client
24
+
25
+ if (grantType !== 'code') {
26
+ throw new Error(`unsupported grant type: ${grantType}`)
27
+ }
28
+
29
+ const state = crypto.randomBytes(16).toString('hex')
30
+ await repository.save({
31
+ ...oauth2Client,
32
+ state
33
+ })
34
+
35
+ var auth = new ClientOAuth2({
36
+ clientId,
37
+ clientSecret,
38
+ accessTokenUri,
39
+ authorizationUri,
40
+ redirectUri: `${origin}/oauth2-client/callback`,
41
+ scopes: scopes?.split(' ') || [],
42
+ state
43
+ })
44
+
45
+ context.status = 200
46
+ context.body = await auth.code.getUri()
47
+ })
48
+
49
+ domainPrivateRouter.get('/oauth2-client/:id/refresh', async (context, next) => {
50
+ const { params } = context
51
+ const { id } = params
52
+
53
+ const repository = getRepository(Oauth2Client)
54
+ const oauth2Client = await repository.findOneBy({ id })
55
+
56
+ const {
57
+ grantType,
58
+ clientId,
59
+ clientSecret,
60
+ accessTokenUrl: accessTokenUri,
61
+ authUrl: authorizationUri,
62
+ scopes,
63
+ tokenType,
64
+ accessToken,
65
+ refreshToken
66
+ } = oauth2Client
67
+
68
+ if (!refreshToken) {
69
+ context.status = 404
70
+ context.body = 'refreshToken not found'
71
+
72
+ return
73
+ }
74
+
75
+ var auth = new ClientOAuth2({
76
+ clientId,
77
+ clientSecret,
78
+ accessTokenUri,
79
+ authorizationUri,
80
+ scopes: scopes?.split(' ') || []
81
+ })
82
+
83
+ const { accessToken: newAccessToken, refreshToken: newRefreshToken } = await auth
84
+ .createToken(accessToken, refreshToken, tokenType, {})
85
+ .refresh()
86
+
87
+ context.status = 200
88
+ context.body = await repository.save({
89
+ ...oauth2Client,
90
+ accessToken: newAccessToken,
91
+ refreshToken: newRefreshToken,
92
+ state: ''
93
+ })
94
+ })
95
+ })
96
+
97
+ process.on('bootstrap-module-domain-public-route' as any, (app, domainPublicRouter) => {
98
+ domainPublicRouter.get('/oauth2-client/callback', async (context, next) => {
99
+ const { state } = context.query
100
+ const { originalUrl } = context
101
+
102
+ const repository = getRepository(Oauth2Client)
103
+ const oauth2Client: Oauth2Client = await repository.findOne({
104
+ where: { state },
105
+ relations: ['domain']
106
+ })
107
+
108
+ const {
109
+ domain,
110
+ id,
111
+ grantType,
112
+ clientId,
113
+ clientSecret,
114
+ accessTokenUrl: accessTokenUri,
115
+ authUrl: authorizationUri,
116
+ scopes
117
+ } = oauth2Client
118
+
119
+ var auth = new ClientOAuth2({
120
+ clientId,
121
+ clientSecret,
122
+ accessTokenUri,
123
+ authorizationUri,
124
+ scopes: scopes?.split(' ') || []
125
+ })
126
+
127
+ var url = originalUrl
128
+ switch (grantType) {
129
+ case 'credentials':
130
+ break
131
+ case 'jwt':
132
+ break
133
+ case 'code':
134
+ break
135
+ case 'owner':
136
+ default:
137
+ throw new Error(`unsupported grant type: ${grantType}`)
138
+ }
139
+
140
+ const token = await auth.code.getToken(originalUrl)
141
+ const { tokenType, accessToken, refreshToken, data } = token
142
+ const expires = data?.expires_in ? token.expiresIn(data?.expires_in as any) : null
143
+
144
+ await repository.save({
145
+ ...oauth2Client,
146
+ tokenType,
147
+ accessToken,
148
+ refreshToken,
149
+ expires,
150
+ scopes: data?.scope?.replace(',', ' '),
151
+ state: ''
152
+ })
153
+
154
+ context.redirect(getRedirectSubdomainPath(context, domain?.subdomain, `/oauth2-client/${id}`))
155
+ })
156
+ })
@@ -0,0 +1,17 @@
1
+ /* IMPORT ENTITIES AND RESOLVERS */
2
+ import { entities as Oauth2ClientEntities, resolvers as Oauth2ClientResolvers } from './oauth2-client'
3
+
4
+ /* EXPORT ENTITY TYPES */
5
+ export * from './oauth2-client/oauth2-client'
6
+
7
+ export const entities = [
8
+ /* ENTITIES */
9
+ ...Oauth2ClientEntities
10
+ ]
11
+
12
+ export const schema = {
13
+ resolverClasses: [
14
+ /* RESOLVER CLASSES */
15
+ ...Oauth2ClientResolvers
16
+ ]
17
+ }
@@ -0,0 +1,6 @@
1
+ import { Oauth2Client } from './oauth2-client'
2
+ import { Oauth2ClientQuery } from './oauth2-client-query'
3
+ import { Oauth2ClientMutation } from './oauth2-client-mutation'
4
+
5
+ export const entities = [Oauth2Client]
6
+ export const resolvers = [Oauth2ClientQuery, Oauth2ClientMutation]
@@ -0,0 +1,202 @@
1
+ import ClientOAuth2 from 'client-oauth2'
2
+ import crypto from 'crypto'
3
+ import { Arg, Ctx, Directive, Mutation, Resolver } from 'type-graphql'
4
+ import { In } from 'typeorm'
5
+
6
+ import { config } from '@things-factory/env'
7
+
8
+ import { Oauth2Client } from './oauth2-client'
9
+ import { NewOauth2Client, Oauth2ClientPatch } from './oauth2-client-type'
10
+
11
+ const protocol: string = config.get('protocol')
12
+
13
+ @Resolver(Oauth2Client)
14
+ export class Oauth2ClientMutation {
15
+ @Directive('@transaction')
16
+ @Mutation(returns => Oauth2Client, { description: 'To create new Oauth2Client' })
17
+ async createOauth2Client(
18
+ @Arg('oauth2Client') oauth2Client: NewOauth2Client,
19
+ @Ctx() context: ResolverContext
20
+ ): Promise<Oauth2Client> {
21
+ const { domain, user, tx } = context.state
22
+
23
+ const originalProtocol = context.headers['x-forwarded-proto']
24
+ const originalHost = context.headers['x-forwarded-host']
25
+ const originalPort = context.headers['x-forwarded-port']
26
+
27
+ if (originalProtocol && originalHost) {
28
+ var url: URL = new URL(`${originalProtocol}://${originalHost}`)
29
+ if (originalPort) {
30
+ url.port = originalPort
31
+ }
32
+ } else {
33
+ var url: URL = new URL(context.request.origin)
34
+ }
35
+
36
+ if (protocol) {
37
+ url.protocol = protocol
38
+ }
39
+
40
+ url.pathname = '/oauth2-client/callback'
41
+
42
+ return await tx.getRepository(Oauth2Client).save({
43
+ ...oauth2Client,
44
+ callbackUrl: oauth2Client.callbackUrl || url.href,
45
+ domain,
46
+ creator: user,
47
+ updater: user
48
+ })
49
+ }
50
+
51
+ @Directive('@transaction')
52
+ @Mutation(returns => Oauth2Client, { description: 'To modify Oauth2Client information' })
53
+ async updateOauth2Client(
54
+ @Arg('id') id: string,
55
+ @Arg('patch') patch: Oauth2ClientPatch,
56
+ @Ctx() context: ResolverContext
57
+ ): Promise<Oauth2Client> {
58
+ const { domain, user, tx } = context.state
59
+
60
+ const repository = tx.getRepository(Oauth2Client)
61
+ const oauth2Client = await repository.findOne({
62
+ where: { domain: { id: domain.id }, id }
63
+ })
64
+
65
+ return await repository.save({
66
+ ...oauth2Client,
67
+ ...patch,
68
+ updater: user
69
+ })
70
+ }
71
+
72
+ @Directive('@transaction')
73
+ @Mutation(returns => Boolean, { description: 'To delete Oauth2Client' })
74
+ async deleteOauth2Client(@Arg('id') id: string, @Ctx() context: ResolverContext): Promise<boolean> {
75
+ const { domain, tx } = context.state
76
+
77
+ await tx.getRepository(Oauth2Client).delete({ domain: { id: domain.id }, id })
78
+
79
+ return true
80
+ }
81
+
82
+ @Directive('@transaction')
83
+ @Mutation(returns => Boolean, { description: 'To delete multiple Oauth2Clients' })
84
+ async deleteOauth2Clients(
85
+ @Arg('ids', type => [String]) ids: string[],
86
+ @Ctx() context: ResolverContext
87
+ ): Promise<boolean> {
88
+ const { domain, tx } = context.state
89
+
90
+ await tx.getRepository(Oauth2Client).delete({
91
+ domain: { id: domain.id },
92
+ id: In(ids)
93
+ })
94
+
95
+ return true
96
+ }
97
+
98
+ @Directive('@transaction')
99
+ @Mutation(returns => Boolean, { description: 'To import multiple Oauth2Clients' })
100
+ async importOauth2Clients(
101
+ @Arg('oauth2Clients', type => [Oauth2ClientPatch]) oauth2Clients: Oauth2ClientPatch[],
102
+ @Ctx() context: ResolverContext
103
+ ): Promise<boolean> {
104
+ const { domain, tx } = context.state
105
+
106
+ await Promise.all(
107
+ oauth2Clients.map(async (oauth2Client: Oauth2ClientPatch) => {
108
+ const createdOauth2Client: Oauth2Client = await tx.getRepository(Oauth2Client).save({ domain, ...oauth2Client })
109
+ })
110
+ )
111
+
112
+ return true
113
+ }
114
+
115
+ @Directive('@transaction')
116
+ @Mutation(returns => String, { description: 'To get oauth2 auth URL' })
117
+ async getOauth2AuthUrl(@Arg('id') id: string, @Ctx() context: ResolverContext): Promise<string> {
118
+ const { tx } = context.state
119
+ const repository = tx.getRepository(Oauth2Client)
120
+ const oauth2Client = await repository.findOneBy({ id })
121
+
122
+ const {
123
+ grantType,
124
+ clientId,
125
+ clientSecret,
126
+ callbackUrl,
127
+ accessTokenUrl: accessTokenUri,
128
+ authUrl: authorizationUri,
129
+ scopes
130
+ } = oauth2Client
131
+
132
+ if (grantType !== 'code') {
133
+ throw new Error(`unsupported grant type: ${grantType}`)
134
+ }
135
+
136
+ const state = crypto.randomBytes(16).toString('hex')
137
+ await repository.save({
138
+ ...oauth2Client,
139
+ state
140
+ })
141
+
142
+ var auth = new ClientOAuth2({
143
+ clientId,
144
+ clientSecret,
145
+ accessTokenUri,
146
+ authorizationUri,
147
+ redirectUri: callbackUrl || `${context.origin}/oauth2-client/callback`,
148
+ scopes: scopes?.split(' ') || [],
149
+ state
150
+ })
151
+
152
+ return await (auth[grantType] as any).getUri()
153
+ }
154
+
155
+ @Directive('@transaction')
156
+ @Mutation(returns => Oauth2Client, { description: 'To refresh oauth2 access token' })
157
+ async refreshOauth2AccessToken(@Arg('id') id: string, @Ctx() context: ResolverContext): Promise<Oauth2Client> {
158
+ const { tx } = context.state
159
+ const repository = tx.getRepository(Oauth2Client)
160
+ const oauth2Client = await repository.findOneBy({ id })
161
+
162
+ const {
163
+ clientId,
164
+ clientSecret,
165
+ accessTokenUrl: accessTokenUri,
166
+ authUrl: authorizationUri,
167
+ scopes,
168
+ tokenType,
169
+ accessToken,
170
+ refreshToken
171
+ } = oauth2Client
172
+
173
+ if (!refreshToken) {
174
+ throw new Error('refreshToken not found')
175
+ }
176
+
177
+ var auth = new ClientOAuth2({
178
+ clientId,
179
+ clientSecret,
180
+ accessTokenUri,
181
+ authorizationUri,
182
+ scopes: scopes?.split(' ')
183
+ })
184
+
185
+ try {
186
+ var token = await auth.createToken(accessToken, refreshToken, tokenType, {}).refresh()
187
+ } catch (err) {
188
+ throw err
189
+ }
190
+
191
+ const { accessToken: newAccessToken, refreshToken: newRefreshToken, tokenType: newTokenType, data } = token
192
+ const expires = data?.expires_in ? token.expiresIn(data?.expires_in as any) : null
193
+
194
+ return await repository.save({
195
+ ...oauth2Client,
196
+ accessToken: newAccessToken,
197
+ refreshToken: newRefreshToken,
198
+ tokenType: newTokenType,
199
+ expires
200
+ })
201
+ }
202
+ }
@@ -0,0 +1,53 @@
1
+ import { Arg, Args, Ctx, FieldResolver, Query, Resolver, Root } from 'type-graphql'
2
+
3
+ import { User } from '@things-factory/auth-base'
4
+ import { Domain, getQueryBuilderFromListParams, getRepository, ListParam } from '@things-factory/shell'
5
+
6
+ import { Oauth2Client } from './oauth2-client'
7
+ import { Oauth2ClientList } from './oauth2-client-type'
8
+
9
+ @Resolver(Oauth2Client)
10
+ export class Oauth2ClientQuery {
11
+ @Query(returns => Oauth2Client!, { nullable: true, description: 'To fetch a Oauth2Client' })
12
+ async oauth2Client(@Arg('id') id: string, @Ctx() context: ResolverContext): Promise<Oauth2Client> {
13
+ const { domain } = context.state
14
+
15
+ return await getRepository(Oauth2Client).findOne({
16
+ where: { domain: { id: domain.id }, id }
17
+ })
18
+ }
19
+
20
+ @Query(returns => Oauth2ClientList, { description: 'To fetch multiple Oauth2Clients' })
21
+ async oauth2Clients(
22
+ @Args(type => ListParam) params: ListParam,
23
+ @Ctx() context: ResolverContext
24
+ ): Promise<Oauth2ClientList> {
25
+ const { domain } = context.state
26
+
27
+ const queryBuilder = getQueryBuilderFromListParams({
28
+ domain,
29
+ params,
30
+ repository: await getRepository(Oauth2Client),
31
+ searchables: ['name', 'description']
32
+ })
33
+
34
+ const [items, total] = await queryBuilder.getManyAndCount()
35
+
36
+ return { items, total }
37
+ }
38
+
39
+ @FieldResolver(type => Domain)
40
+ async domain(@Root() oauth2Client: Oauth2Client): Promise<Domain> {
41
+ return await getRepository(Domain).findOneBy({ id: oauth2Client.domainId })
42
+ }
43
+
44
+ @FieldResolver(type => User)
45
+ async updater(@Root() oauth2Client: Oauth2Client): Promise<User> {
46
+ return await getRepository(User).findOneBy({ id: oauth2Client.updaterId })
47
+ }
48
+
49
+ @FieldResolver(type => User)
50
+ async creator(@Root() oauth2Client: Oauth2Client): Promise<User> {
51
+ return await getRepository(User).findOneBy({ id: oauth2Client.creatorId })
52
+ }
53
+ }
@@ -0,0 +1,127 @@
1
+ import { Field, InputType, Int, ObjectType } from 'type-graphql'
2
+ import { GraphQLJWT } from 'graphql-scalars'
3
+
4
+ import { Oauth2Client } from './oauth2-client'
5
+
6
+ @InputType()
7
+ export class NewOauth2Client {
8
+ @Field()
9
+ name: string
10
+
11
+ @Field({ nullable: true })
12
+ description?: string
13
+
14
+ @Field({ nullable: true })
15
+ icon?: string
16
+
17
+ @Field({ nullable: true })
18
+ grantType?: string
19
+
20
+ @Field({ nullable: true })
21
+ clientId?: string
22
+
23
+ @Field({ nullable: true })
24
+ clientSecret?: string
25
+
26
+ @Field({ nullable: true })
27
+ callbackUrl?: string
28
+
29
+ @Field({ nullable: true })
30
+ authUrl?: string
31
+
32
+ @Field({ nullable: true })
33
+ accessTokenUrl?: string
34
+
35
+ @Field({ nullable: true })
36
+ webhook?: string
37
+
38
+ @Field({ nullable: true })
39
+ username?: string
40
+
41
+ @Field({ nullable: true })
42
+ password?: string
43
+
44
+ @Field({ nullable: true })
45
+ codeChallengeMethod?: string
46
+
47
+ @Field({ nullable: true })
48
+ codeVerifier?: string
49
+
50
+ @Field({ nullable: true })
51
+ scopes?: string
52
+
53
+ @Field({ nullable: true })
54
+ accessToken?: string
55
+
56
+ @Field({ nullable: true })
57
+ refreshToken?: string
58
+
59
+ @Field(type => GraphQLJWT, { nullable: true })
60
+ jwtToken?: string
61
+ }
62
+
63
+ @InputType()
64
+ export class Oauth2ClientPatch {
65
+ @Field({ nullable: true })
66
+ name?: string
67
+
68
+ @Field({ nullable: true })
69
+ description?: string
70
+
71
+ @Field({ nullable: true })
72
+ icon?: string
73
+
74
+ @Field({ nullable: true })
75
+ grantType?: string
76
+
77
+ @Field({ nullable: true })
78
+ clientId?: string
79
+
80
+ @Field({ nullable: true })
81
+ clientSecret?: string
82
+
83
+ @Field({ nullable: true })
84
+ callbackUrl?: string
85
+
86
+ @Field({ nullable: true })
87
+ authUrl?: string
88
+
89
+ @Field({ nullable: true })
90
+ accessTokenUrl?: string
91
+
92
+ @Field({ nullable: true })
93
+ webhook?: string
94
+
95
+ @Field({ nullable: true })
96
+ username?: string
97
+
98
+ @Field({ nullable: true })
99
+ password?: string
100
+
101
+ @Field({ nullable: true })
102
+ codeChallengeMethod?: string
103
+
104
+ @Field({ nullable: true })
105
+ codeVerifier?: string
106
+
107
+ @Field({ nullable: true })
108
+ scopes?: string
109
+
110
+ @Field({ nullable: true })
111
+ accessToken?: string
112
+
113
+ @Field({ nullable: true })
114
+ refreshToken?: string
115
+
116
+ @Field(type => GraphQLJWT, { nullable: true })
117
+ jwtToken?: string
118
+ }
119
+
120
+ @ObjectType()
121
+ export class Oauth2ClientList {
122
+ @Field(type => [Oauth2Client])
123
+ items: Oauth2Client[]
124
+
125
+ @Field(type => Int)
126
+ total: number
127
+ }