From ac58967d7a2fdfdfd22cf46f138663a2a7546251 Mon Sep 17 00:00:00 2001 From: cbolles Date: Thu, 4 Jan 2024 15:07:27 -0500 Subject: [PATCH 1/5] Add auth microservice module --- package-lock.json | 267 ++++++++++++++ packages/server/package.json | 4 +- packages/server/src/app.module.ts | 6 +- packages/server/src/auth/auth.module.ts | 4 + packages/server/src/auth/graphql-codegen.yml | 23 ++ packages/server/src/auth/graphql/graphql.ts | 347 ++++++++++++++++++ packages/server/src/auth/graphql/sdk.ts | 36 ++ .../src/auth/graphql/users/users.graphql | 13 + .../server/src/auth/graphql/users/users.ts | 36 ++ packages/server/src/user/user.module.ts | 8 + packages/server/src/user/user.service.ts | 6 + 11 files changed, 748 insertions(+), 2 deletions(-) create mode 100644 packages/server/src/auth/auth.module.ts create mode 100644 packages/server/src/auth/graphql-codegen.yml create mode 100644 packages/server/src/auth/graphql/graphql.ts create mode 100644 packages/server/src/auth/graphql/sdk.ts create mode 100644 packages/server/src/auth/graphql/users/users.graphql create mode 100644 packages/server/src/auth/graphql/users/users.ts create mode 100644 packages/server/src/user/user.module.ts create mode 100644 packages/server/src/user/user.service.ts diff --git a/package-lock.json b/package-lock.json index 5799b0e2..47f77882 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4348,6 +4348,148 @@ "graphql": "^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0" } }, + "node_modules/@graphql-codegen/typescript-generic-sdk": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@graphql-codegen/typescript-generic-sdk/-/typescript-generic-sdk-4.0.0.tgz", + "integrity": "sha512-5tBHoIEEqvF5JVJpvyIGF9/zRNPYGJJU3hT9OWHBE759Fj0Q48O4BhZfBABtK64R/R0iWBmZWmt0HKIV/6b0Xg==", + "dev": true, + "dependencies": { + "@graphql-codegen/plugin-helpers": "^3.0.0", + "@graphql-codegen/visitor-plugin-common": "2.13.1", + "auto-bind": "~4.0.0", + "tslib": "~2.6.0" + }, + "engines": { + "node": ">= 16.0.0" + }, + "peerDependencies": { + "graphql": "^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0", + "graphql-tag": "^2.0.0" + } + }, + "node_modules/@graphql-codegen/typescript-generic-sdk/node_modules/@graphql-codegen/plugin-helpers": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@graphql-codegen/plugin-helpers/-/plugin-helpers-3.1.2.tgz", + "integrity": "sha512-emOQiHyIliVOIjKVKdsI5MXj312zmRDwmHpyUTZMjfpvxq/UVAHUJIVdVf+lnjjrI+LXBTgMlTWTgHQfmICxjg==", + "dev": true, + "dependencies": { + "@graphql-tools/utils": "^9.0.0", + "change-case-all": "1.0.15", + "common-tags": "1.8.2", + "import-from": "4.0.0", + "lodash": "~4.17.0", + "tslib": "~2.4.0" + }, + "peerDependencies": { + "graphql": "^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0" + } + }, + "node_modules/@graphql-codegen/typescript-generic-sdk/node_modules/@graphql-codegen/plugin-helpers/node_modules/tslib": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", + "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==", + "dev": true + }, + "node_modules/@graphql-codegen/typescript-generic-sdk/node_modules/@graphql-codegen/visitor-plugin-common": { + "version": "2.13.1", + "resolved": "https://registry.npmjs.org/@graphql-codegen/visitor-plugin-common/-/visitor-plugin-common-2.13.1.tgz", + "integrity": "sha512-mD9ufZhDGhyrSaWQGrU1Q1c5f01TeWtSWy/cDwXYjJcHIj1Y/DG2x0tOflEfCvh5WcnmHNIw4lzDsg1W7iFJEg==", + "dev": true, + "dependencies": { + "@graphql-codegen/plugin-helpers": "^2.7.2", + "@graphql-tools/optimize": "^1.3.0", + "@graphql-tools/relay-operation-optimizer": "^6.5.0", + "@graphql-tools/utils": "^8.8.0", + "auto-bind": "~4.0.0", + "change-case-all": "1.0.14", + "dependency-graph": "^0.11.0", + "graphql-tag": "^2.11.0", + "parse-filepath": "^1.0.2", + "tslib": "~2.4.0" + }, + "peerDependencies": { + "graphql": "^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0" + } + }, + "node_modules/@graphql-codegen/typescript-generic-sdk/node_modules/@graphql-codegen/visitor-plugin-common/node_modules/@graphql-codegen/plugin-helpers": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/@graphql-codegen/plugin-helpers/-/plugin-helpers-2.7.2.tgz", + "integrity": "sha512-kln2AZ12uii6U59OQXdjLk5nOlh1pHis1R98cDZGFnfaiAbX9V3fxcZ1MMJkB7qFUymTALzyjZoXXdyVmPMfRg==", + "dev": true, + "dependencies": { + "@graphql-tools/utils": "^8.8.0", + "change-case-all": "1.0.14", + "common-tags": "1.8.2", + "import-from": "4.0.0", + "lodash": "~4.17.0", + "tslib": "~2.4.0" + }, + "peerDependencies": { + "graphql": "^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0" + } + }, + "node_modules/@graphql-codegen/typescript-generic-sdk/node_modules/@graphql-codegen/visitor-plugin-common/node_modules/@graphql-tools/utils": { + "version": "8.13.1", + "resolved": "https://registry.npmjs.org/@graphql-tools/utils/-/utils-8.13.1.tgz", + "integrity": "sha512-qIh9yYpdUFmctVqovwMdheVNJqFh+DQNWIhX87FJStfXYnmweBUDATok9fWPleKeFwxnW8IapKmY8m8toJEkAw==", + "dev": true, + "dependencies": { + "tslib": "^2.4.0" + }, + "peerDependencies": { + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + } + }, + "node_modules/@graphql-codegen/typescript-generic-sdk/node_modules/@graphql-codegen/visitor-plugin-common/node_modules/change-case-all": { + "version": "1.0.14", + "resolved": "https://registry.npmjs.org/change-case-all/-/change-case-all-1.0.14.tgz", + "integrity": "sha512-CWVm2uT7dmSHdO/z1CXT/n47mWonyypzBbuCy5tN7uMg22BsfkhwT6oHmFCAk+gL1LOOxhdbB9SZz3J1KTY3gA==", + "dev": true, + "dependencies": { + "change-case": "^4.1.2", + "is-lower-case": "^2.0.2", + "is-upper-case": "^2.0.2", + "lower-case": "^2.0.2", + "lower-case-first": "^2.0.2", + "sponge-case": "^1.0.1", + "swap-case": "^2.0.2", + "title-case": "^3.0.3", + "upper-case": "^2.0.2", + "upper-case-first": "^2.0.2" + } + }, + "node_modules/@graphql-codegen/typescript-generic-sdk/node_modules/@graphql-codegen/visitor-plugin-common/node_modules/tslib": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", + "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==", + "dev": true + }, + "node_modules/@graphql-codegen/typescript-generic-sdk/node_modules/@graphql-tools/optimize": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@graphql-tools/optimize/-/optimize-1.4.0.tgz", + "integrity": "sha512-dJs/2XvZp+wgHH8T5J2TqptT9/6uVzIYvA6uFACha+ufvdMBedkfR4b4GbT8jAKLRARiqRTxy3dctnwkTM2tdw==", + "dev": true, + "dependencies": { + "tslib": "^2.4.0" + }, + "peerDependencies": { + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + } + }, + "node_modules/@graphql-codegen/typescript-generic-sdk/node_modules/@graphql-tools/relay-operation-optimizer": { + "version": "6.5.18", + "resolved": "https://registry.npmjs.org/@graphql-tools/relay-operation-optimizer/-/relay-operation-optimizer-6.5.18.tgz", + "integrity": "sha512-mc5VPyTeV+LwiM+DNvoDQfPqwQYhPV/cl5jOBjTgSniyaq8/86aODfMkrE2OduhQ5E00hqrkuL2Fdrgk0w1QJg==", + "dev": true, + "dependencies": { + "@ardatan/relay-compiler": "12.0.0", + "@graphql-tools/utils": "^9.2.1", + "tslib": "^2.4.0" + }, + "peerDependencies": { + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + } + }, "node_modules/@graphql-codegen/typescript-operations": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/@graphql-codegen/typescript-operations/-/typescript-operations-4.0.1.tgz", @@ -26388,6 +26530,7 @@ "rxjs": "^7.2.0" }, "devDependencies": { + "@graphql-codegen/typescript-generic-sdk": "^4.0.0", "@nestjs/cli": "^9.0.0", "@nestjs/schematics": "^9.0.0", "@nestjs/testing": "^9.0.0", @@ -29486,6 +29629,129 @@ } } }, + "@graphql-codegen/typescript-generic-sdk": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@graphql-codegen/typescript-generic-sdk/-/typescript-generic-sdk-4.0.0.tgz", + "integrity": "sha512-5tBHoIEEqvF5JVJpvyIGF9/zRNPYGJJU3hT9OWHBE759Fj0Q48O4BhZfBABtK64R/R0iWBmZWmt0HKIV/6b0Xg==", + "dev": true, + "requires": { + "@graphql-codegen/plugin-helpers": "^3.0.0", + "@graphql-codegen/visitor-plugin-common": "2.13.1", + "auto-bind": "~4.0.0", + "tslib": "~2.6.0" + }, + "dependencies": { + "@graphql-codegen/plugin-helpers": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@graphql-codegen/plugin-helpers/-/plugin-helpers-3.1.2.tgz", + "integrity": "sha512-emOQiHyIliVOIjKVKdsI5MXj312zmRDwmHpyUTZMjfpvxq/UVAHUJIVdVf+lnjjrI+LXBTgMlTWTgHQfmICxjg==", + "dev": true, + "requires": { + "@graphql-tools/utils": "^9.0.0", + "change-case-all": "1.0.15", + "common-tags": "1.8.2", + "import-from": "4.0.0", + "lodash": "~4.17.0", + "tslib": "~2.4.0" + }, + "dependencies": { + "tslib": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", + "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==", + "dev": true + } + } + }, + "@graphql-codegen/visitor-plugin-common": { + "version": "2.13.1", + "resolved": "https://registry.npmjs.org/@graphql-codegen/visitor-plugin-common/-/visitor-plugin-common-2.13.1.tgz", + "integrity": "sha512-mD9ufZhDGhyrSaWQGrU1Q1c5f01TeWtSWy/cDwXYjJcHIj1Y/DG2x0tOflEfCvh5WcnmHNIw4lzDsg1W7iFJEg==", + "dev": true, + "requires": { + "@graphql-codegen/plugin-helpers": "^2.7.2", + "@graphql-tools/optimize": "^1.3.0", + "@graphql-tools/relay-operation-optimizer": "^6.5.0", + "@graphql-tools/utils": "^8.8.0", + "auto-bind": "~4.0.0", + "change-case-all": "1.0.14", + "dependency-graph": "^0.11.0", + "graphql-tag": "^2.11.0", + "parse-filepath": "^1.0.2", + "tslib": "~2.4.0" + }, + "dependencies": { + "@graphql-codegen/plugin-helpers": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/@graphql-codegen/plugin-helpers/-/plugin-helpers-2.7.2.tgz", + "integrity": "sha512-kln2AZ12uii6U59OQXdjLk5nOlh1pHis1R98cDZGFnfaiAbX9V3fxcZ1MMJkB7qFUymTALzyjZoXXdyVmPMfRg==", + "dev": true, + "requires": { + "@graphql-tools/utils": "^8.8.0", + "change-case-all": "1.0.14", + "common-tags": "1.8.2", + "import-from": "4.0.0", + "lodash": "~4.17.0", + "tslib": "~2.4.0" + } + }, + "@graphql-tools/utils": { + "version": "8.13.1", + "resolved": "https://registry.npmjs.org/@graphql-tools/utils/-/utils-8.13.1.tgz", + "integrity": "sha512-qIh9yYpdUFmctVqovwMdheVNJqFh+DQNWIhX87FJStfXYnmweBUDATok9fWPleKeFwxnW8IapKmY8m8toJEkAw==", + "dev": true, + "requires": { + "tslib": "^2.4.0" + } + }, + "change-case-all": { + "version": "1.0.14", + "resolved": "https://registry.npmjs.org/change-case-all/-/change-case-all-1.0.14.tgz", + "integrity": "sha512-CWVm2uT7dmSHdO/z1CXT/n47mWonyypzBbuCy5tN7uMg22BsfkhwT6oHmFCAk+gL1LOOxhdbB9SZz3J1KTY3gA==", + "dev": true, + "requires": { + "change-case": "^4.1.2", + "is-lower-case": "^2.0.2", + "is-upper-case": "^2.0.2", + "lower-case": "^2.0.2", + "lower-case-first": "^2.0.2", + "sponge-case": "^1.0.1", + "swap-case": "^2.0.2", + "title-case": "^3.0.3", + "upper-case": "^2.0.2", + "upper-case-first": "^2.0.2" + } + }, + "tslib": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", + "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==", + "dev": true + } + } + }, + "@graphql-tools/optimize": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@graphql-tools/optimize/-/optimize-1.4.0.tgz", + "integrity": "sha512-dJs/2XvZp+wgHH8T5J2TqptT9/6uVzIYvA6uFACha+ufvdMBedkfR4b4GbT8jAKLRARiqRTxy3dctnwkTM2tdw==", + "dev": true, + "requires": { + "tslib": "^2.4.0" + } + }, + "@graphql-tools/relay-operation-optimizer": { + "version": "6.5.18", + "resolved": "https://registry.npmjs.org/@graphql-tools/relay-operation-optimizer/-/relay-operation-optimizer-6.5.18.tgz", + "integrity": "sha512-mc5VPyTeV+LwiM+DNvoDQfPqwQYhPV/cl5jOBjTgSniyaq8/86aODfMkrE2OduhQ5E00hqrkuL2Fdrgk0w1QJg==", + "dev": true, + "requires": { + "@ardatan/relay-compiler": "12.0.0", + "@graphql-tools/utils": "^9.2.1", + "tslib": "^2.4.0" + } + } + } + }, "@graphql-codegen/typescript-operations": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/@graphql-codegen/typescript-operations/-/typescript-operations-4.0.1.tgz", @@ -43152,6 +43418,7 @@ "requires": { "@apollo/subgraph": "^2.4.12", "@google-cloud/storage": "^7.7.0", + "@graphql-codegen/typescript-generic-sdk": "*", "@nestjs/apollo": "^12.0.7", "@nestjs/axios": "^3.0.1", "@nestjs/cli": "^9.0.0", diff --git a/packages/server/package.json b/packages/server/package.json index e43070e2..8e9d74f7 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -19,7 +19,8 @@ "test:watch": "jest --watch", "test:cov": "jest --coverage", "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", - "test:e2e": "jest --config ./test/jest-e2e.json" + "test:e2e": "jest --config ./test/jest-e2e.json", + "introspection": "graphql-codegen --config src/auth/graphql-codegen.yml" }, "dependencies": { "@apollo/subgraph": "^2.4.12", @@ -47,6 +48,7 @@ "rxjs": "^7.2.0" }, "devDependencies": { + "@graphql-codegen/typescript-generic-sdk": "^4.0.0", "@nestjs/cli": "^9.0.0", "@nestjs/schematics": "^9.0.0", "@nestjs/testing": "^9.0.0", diff --git a/packages/server/src/app.module.ts b/packages/server/src/app.module.ts index 23bf25eb..dfc5594f 100644 --- a/packages/server/src/app.module.ts +++ b/packages/server/src/app.module.ts @@ -13,6 +13,8 @@ import { TagModule } from './tag/tag.module'; import { SharedModule } from './shared/shared.module'; import { JwtModule } from './jwt/jwt.module'; import { PermissionModule } from './permission/permission.module'; +import { UserModule } from './user/user.module'; +import { AuthModule } from './auth/auth.module'; @Module({ imports: [ @@ -42,7 +44,9 @@ import { PermissionModule } from './permission/permission.module'; TagModule, SharedModule, JwtModule, - PermissionModule + PermissionModule, + UserModule, + AuthModule ] }) export class AppModule {} diff --git a/packages/server/src/auth/auth.module.ts b/packages/server/src/auth/auth.module.ts new file mode 100644 index 00000000..7459c064 --- /dev/null +++ b/packages/server/src/auth/auth.module.ts @@ -0,0 +1,4 @@ +import { Module } from '@nestjs/common'; + +@Module({}) +export class AuthModule {} diff --git a/packages/server/src/auth/graphql-codegen.yml b/packages/server/src/auth/graphql-codegen.yml new file mode 100644 index 00000000..53ece2c8 --- /dev/null +++ b/packages/server/src/auth/graphql-codegen.yml @@ -0,0 +1,23 @@ +overwrite: true +schema: https://test-auth-service.sail.codes/graphql +documents: src/auth/graphql/**/*.graphql +generates: + src/auth/graphql/graphql.ts: + plugins: + - add: + content: '/* Generated File DO NOT EDIT. */' + - add: + content: '/* tslint:disable */' + - typescript + src/auth/graphql/sdk.ts: + documents: src/auth/graphql/**/*.graphql + presetConfig: + baseTypesPath: graphql.ts + extension: .ts + plugins: + - add: + content: '/* Generated File DO NOT EDIT. */' + - add: + content: '/* tslint:disable */' + - typescript-operations + - typescript-generic-sdk diff --git a/packages/server/src/auth/graphql/graphql.ts b/packages/server/src/auth/graphql/graphql.ts new file mode 100644 index 00000000..db2208fd --- /dev/null +++ b/packages/server/src/auth/graphql/graphql.ts @@ -0,0 +1,347 @@ +/* Generated File DO NOT EDIT. */ +/* tslint:disable */ +export type Maybe = T | null; +export type InputMaybe = Maybe; +export type Exact = { [K in keyof T]: T[K] }; +export type MakeOptional = Omit & { [SubKey in K]?: Maybe }; +export type MakeMaybe = Omit & { [SubKey in K]: Maybe }; +export type MakeEmpty = { [_ in K]?: never }; +export type Incremental = T | { [P in keyof T]?: P extends ' $fragmentName' | '__typename' ? T[P] : never }; +/** All built-in and custom scalars, mapped to their actual values */ +export type Scalars = { + ID: { input: string; output: string; } + String: { input: string; output: string; } + Boolean: { input: boolean; output: boolean; } + Int: { input: number; output: number; } + Float: { input: number; output: number; } + DateTime: { input: any; output: any; } + JSON: { input: any; output: any; } + _Any: { input: any; output: any; } + federation__FieldSet: { input: any; output: any; } + link__Import: { input: any; output: any; } +}; + +/** Input type for accepting an invite */ +export type AcceptInviteModel = { + /** The email address of the user accepting the invite */ + email: Scalars['String']['input']; + /** The full name of the user accepting the invite */ + fullname: Scalars['String']['input']; + /** The invite code that was included in the invite email */ + inviteCode: Scalars['String']['input']; + /** The password for the new user account */ + password: Scalars['String']['input']; + /** The ID of the project the invite is associated with */ + projectId: Scalars['String']['input']; +}; + +export type AccessToken = { + __typename?: 'AccessToken'; + accessToken: Scalars['String']['output']; + refreshToken: Scalars['String']['output']; +}; + +export type ConfigurableProjectSettings = { + description?: InputMaybe; + homePage?: InputMaybe; + logo?: InputMaybe; + muiTheme?: InputMaybe; + name?: InputMaybe; + redirectUrl?: InputMaybe; +}; + +export type EmailLoginDto = { + email: Scalars['String']['input']; + password: Scalars['String']['input']; + projectId: Scalars['String']['input']; +}; + +export type ForgotDto = { + email: Scalars['String']['input']; + projectId: Scalars['String']['input']; +}; + +export type GoogleLoginDto = { + credential: Scalars['String']['input']; + projectId: Scalars['String']['input']; +}; + +export type InviteModel = { + __typename?: 'InviteModel'; + /** The date and time at which the invitation was created. */ + createdAt: Scalars['DateTime']['output']; + /** The date and time at which the invitation was deleted, if applicable. */ + deletedAt?: Maybe; + /** The email address of the user being invited. */ + email: Scalars['String']['output']; + /** The date and time at which the invitation expires. */ + expiresAt: Scalars['DateTime']['output']; + /** The ID of the invitation. */ + id: Scalars['ID']['output']; + /** The ID of the project to which the invitation belongs. */ + projectId: Scalars['String']['output']; + /** The role that the user being invited will have. */ + role: Scalars['Int']['output']; + /** The status of the invitation. */ + status: InviteStatus; + /** The date and time at which the invitation was last updated. */ + updatedAt: Scalars['DateTime']['output']; +}; + +/** The status of an invite */ +export enum InviteStatus { + Accepted = 'ACCEPTED', + Cancelled = 'CANCELLED', + Expired = 'EXPIRED', + Pending = 'PENDING' +} + +export type Mutation = { + __typename?: 'Mutation'; + acceptInvite: InviteModel; + cancelInvite: InviteModel; + createInvite: InviteModel; + createProject: ProjectModel; + forgotPassword: Scalars['Boolean']['output']; + loginEmail: AccessToken; + loginGoogle: AccessToken; + loginUsername: AccessToken; + refresh: AccessToken; + resendInvite: InviteModel; + resetPassword: Scalars['Boolean']['output']; + signup: AccessToken; + updateProject: ProjectModel; + updateProjectAuthMethods: ProjectModel; + updateProjectSettings: ProjectModel; + updateUser: UserModel; +}; + + +export type MutationAcceptInviteArgs = { + input: AcceptInviteModel; +}; + + +export type MutationCancelInviteArgs = { + id: Scalars['ID']['input']; +}; + + +export type MutationCreateInviteArgs = { + email: Scalars['String']['input']; + role?: InputMaybe; +}; + + +export type MutationCreateProjectArgs = { + project: ProjectCreateInput; +}; + + +export type MutationForgotPasswordArgs = { + user: ForgotDto; +}; + + +export type MutationLoginEmailArgs = { + user: EmailLoginDto; +}; + + +export type MutationLoginGoogleArgs = { + user: GoogleLoginDto; +}; + + +export type MutationLoginUsernameArgs = { + user: UsernameLoginDto; +}; + + +export type MutationRefreshArgs = { + refreshToken: Scalars['String']['input']; +}; + + +export type MutationResendInviteArgs = { + id: Scalars['ID']['input']; +}; + + +export type MutationResetPasswordArgs = { + user: ResetDto; +}; + + +export type MutationSignupArgs = { + user: UserSignupDto; +}; + + +export type MutationUpdateProjectArgs = { + id: Scalars['String']['input']; + settings: ConfigurableProjectSettings; +}; + + +export type MutationUpdateProjectAuthMethodsArgs = { + id: Scalars['String']['input']; + projectAuthMethods: ProjectAuthMethodsInput; +}; + + +export type MutationUpdateProjectSettingsArgs = { + id: Scalars['String']['input']; + projectSettings: ProjectSettingsInput; +}; + + +export type MutationUpdateUserArgs = { + email: Scalars['String']['input']; + fullname: Scalars['String']['input']; +}; + +export type ProjectAuthMethodsInput = { + emailAuth?: InputMaybe; + googleAuth?: InputMaybe; +}; + +export type ProjectAuthMethodsModel = { + __typename?: 'ProjectAuthMethodsModel'; + emailAuth: Scalars['Boolean']['output']; + googleAuth: Scalars['Boolean']['output']; +}; + +export type ProjectCreateInput = { + allowSignup?: InputMaybe; + description?: InputMaybe; + displayProjectName?: InputMaybe; + emailAuth?: InputMaybe; + googleAuth?: InputMaybe; + homePage?: InputMaybe; + logo?: InputMaybe; + muiTheme?: InputMaybe; + name: Scalars['String']['input']; + redirectUrl?: InputMaybe; +}; + +export type ProjectModel = { + __typename?: 'ProjectModel'; + authMethods: ProjectAuthMethodsModel; + createdAt: Scalars['DateTime']['output']; + deletedAt?: Maybe; + description?: Maybe; + homePage?: Maybe; + id: Scalars['ID']['output']; + logo?: Maybe; + muiTheme: Scalars['JSON']['output']; + name: Scalars['String']['output']; + redirectUrl?: Maybe; + settings: ProjectSettingsModel; + updatedAt: Scalars['DateTime']['output']; + users: Array; +}; + +export type ProjectSettingsInput = { + allowSignup?: InputMaybe; + displayProjectName?: InputMaybe; +}; + +export type ProjectSettingsModel = { + __typename?: 'ProjectSettingsModel'; + allowSignup: Scalars['Boolean']['output']; + displayProjectName: Scalars['Boolean']['output']; +}; + +export type Query = { + __typename?: 'Query'; + _entities: Array>; + _service: _Service; + getProject: ProjectModel; + getUser: UserModel; + invite: InviteModel; + invites: Array; + listProjects: Array; + me: UserModel; + projectUsers: Array; + publicKey: Array; + users: Array; +}; + + +export type Query_EntitiesArgs = { + representations: Array; +}; + + +export type QueryGetProjectArgs = { + id: Scalars['String']['input']; +}; + + +export type QueryGetUserArgs = { + id: Scalars['ID']['input']; +}; + + +export type QueryInviteArgs = { + id: Scalars['ID']['input']; +}; + + +export type QueryInvitesArgs = { + status?: InputMaybe; +}; + + +export type QueryProjectUsersArgs = { + projectId: Scalars['String']['input']; +}; + +export type ResetDto = { + code: Scalars['String']['input']; + email: Scalars['String']['input']; + password: Scalars['String']['input']; + projectId: Scalars['String']['input']; +}; + +export type UserModel = { + __typename?: 'UserModel'; + createdAt: Scalars['DateTime']['output']; + deletedAt?: Maybe; + email?: Maybe; + fullname?: Maybe; + id: Scalars['ID']['output']; + projectId: Scalars['String']['output']; + role: Scalars['Int']['output']; + updatedAt: Scalars['DateTime']['output']; + username?: Maybe; +}; + +export type UserSignupDto = { + email: Scalars['String']['input']; + fullname: Scalars['String']['input']; + password: Scalars['String']['input']; + projectId: Scalars['String']['input']; + username?: InputMaybe; +}; + +export type UsernameLoginDto = { + password: Scalars['String']['input']; + projectId: Scalars['String']['input']; + username: Scalars['String']['input']; +}; + +export type _Entity = InviteModel | ProjectModel | UserModel; + +export type _Service = { + __typename?: '_Service'; + sdl?: Maybe; +}; + +export enum Link__Purpose { + /** `EXECUTION` features provide metadata necessary for operation execution. */ + Execution = 'EXECUTION', + /** `SECURITY` features provide metadata necessary to securely resolve fields. */ + Security = 'SECURITY' +} diff --git a/packages/server/src/auth/graphql/sdk.ts b/packages/server/src/auth/graphql/sdk.ts new file mode 100644 index 00000000..57677063 --- /dev/null +++ b/packages/server/src/auth/graphql/sdk.ts @@ -0,0 +1,36 @@ +/* Generated File DO NOT EDIT. */ +/* tslint:disable */ +import { DocumentNode } from 'graphql'; +import gql from 'graphql-tag'; +export type ProjectUsersQueryVariables = Exact<{ + projectId: Scalars['String']['input']; +}>; + + +export type ProjectUsersQuery = { __typename?: 'Query', projectUsers: Array<{ __typename?: 'UserModel', id: string, projectId: string, username?: string | null, fullname?: string | null, email?: string | null, role: number, createdAt: any, updatedAt: any, deletedAt?: any | null }> }; + + +export const ProjectUsersDocument = gql` + query projectUsers($projectId: String!) { + projectUsers(projectId: $projectId) { + id + projectId + username + fullname + email + role + createdAt + updatedAt + deletedAt + } +} + `; +export type Requester = (doc: DocumentNode, vars?: V, options?: C) => Promise | AsyncIterable +export function getSdk(requester: Requester) { + return { + projectUsers(variables: ProjectUsersQueryVariables, options?: C): Promise { + return requester(ProjectUsersDocument, variables, options) as Promise; + } + }; +} +export type Sdk = ReturnType; \ No newline at end of file diff --git a/packages/server/src/auth/graphql/users/users.graphql b/packages/server/src/auth/graphql/users/users.graphql new file mode 100644 index 00000000..5aa9dc9d --- /dev/null +++ b/packages/server/src/auth/graphql/users/users.graphql @@ -0,0 +1,13 @@ +query projectUsers($projectId: String!) { + projectUsers(projectId: $projectId) { + id + projectId + username + fullname + email + role + createdAt + updatedAt + deletedAt + } +} diff --git a/packages/server/src/auth/graphql/users/users.ts b/packages/server/src/auth/graphql/users/users.ts new file mode 100644 index 00000000..f24eeaa9 --- /dev/null +++ b/packages/server/src/auth/graphql/users/users.ts @@ -0,0 +1,36 @@ +/* Generated File DO NOT EDIT. */ +/* tslint:disable */ +import * as Types from '../graphql'; + +import { DocumentNode } from 'graphql'; +import gql from 'graphql-tag'; +export type UsersQueryVariables = Types.Exact<{ [key: string]: never; }>; + + +export type UsersQuery = { __typename?: 'Query', users: Array<{ __typename?: 'UserModel', id: string, projectId: string, username?: string | null, fullname?: string | null, email?: string | null, role: number, createdAt: any, updatedAt: any, deletedAt?: any | null }> }; + + +export const UsersDocument = gql` + query users { + users { + id + projectId + username + fullname + email + role + createdAt + updatedAt + deletedAt + } +} + `; +export type Requester = (doc: DocumentNode, vars?: V, options?: C) => Promise | AsyncIterable +export function getSdk(requester: Requester) { + return { + users(variables?: UsersQueryVariables, options?: C): Promise { + return requester(UsersDocument, variables, options) as Promise; + } + }; +} +export type Sdk = ReturnType; \ No newline at end of file diff --git a/packages/server/src/user/user.module.ts b/packages/server/src/user/user.module.ts new file mode 100644 index 00000000..8a961e24 --- /dev/null +++ b/packages/server/src/user/user.module.ts @@ -0,0 +1,8 @@ +import { Module } from '@nestjs/common'; +import { UserService } from './user.service'; + +@Module({ + providers: [UserService], + exports: [UserService] +}) +export class UserModule {} diff --git a/packages/server/src/user/user.service.ts b/packages/server/src/user/user.service.ts new file mode 100644 index 00000000..81b978ae --- /dev/null +++ b/packages/server/src/user/user.service.ts @@ -0,0 +1,6 @@ +import { Injectable } from '@nestjs/common'; +import { sdk } from '../graphql/sdk'; + +@Injectable() +export class UserService { +} From 737945378193741fb179f9e0d3038f96ff8c46ac Mon Sep 17 00:00:00 2001 From: cbolles Date: Thu, 4 Jan 2024 15:13:17 -0500 Subject: [PATCH 2/5] Have auth microservice be a dedicated module --- packages/server/src/app.module.ts | 2 - packages/server/src/auth/graphql-codegen.yml | 1 + packages/server/src/auth/graphql/sdk.ts | 346 ++++++++++++++++++ .../server/src/auth/providers/sdk.provider.ts | 12 + packages/server/src/user/user.module.ts | 8 - packages/server/src/user/user.service.ts | 6 - 6 files changed, 359 insertions(+), 16 deletions(-) create mode 100644 packages/server/src/auth/providers/sdk.provider.ts delete mode 100644 packages/server/src/user/user.module.ts delete mode 100644 packages/server/src/user/user.service.ts diff --git a/packages/server/src/app.module.ts b/packages/server/src/app.module.ts index dfc5594f..d35a1669 100644 --- a/packages/server/src/app.module.ts +++ b/packages/server/src/app.module.ts @@ -13,7 +13,6 @@ import { TagModule } from './tag/tag.module'; import { SharedModule } from './shared/shared.module'; import { JwtModule } from './jwt/jwt.module'; import { PermissionModule } from './permission/permission.module'; -import { UserModule } from './user/user.module'; import { AuthModule } from './auth/auth.module'; @Module({ @@ -45,7 +44,6 @@ import { AuthModule } from './auth/auth.module'; SharedModule, JwtModule, PermissionModule, - UserModule, AuthModule ] }) diff --git a/packages/server/src/auth/graphql-codegen.yml b/packages/server/src/auth/graphql-codegen.yml index 53ece2c8..dc1fda2e 100644 --- a/packages/server/src/auth/graphql-codegen.yml +++ b/packages/server/src/auth/graphql-codegen.yml @@ -19,5 +19,6 @@ generates: content: '/* Generated File DO NOT EDIT. */' - add: content: '/* tslint:disable */' + - typescript - typescript-operations - typescript-generic-sdk diff --git a/packages/server/src/auth/graphql/sdk.ts b/packages/server/src/auth/graphql/sdk.ts index 57677063..621989be 100644 --- a/packages/server/src/auth/graphql/sdk.ts +++ b/packages/server/src/auth/graphql/sdk.ts @@ -2,6 +2,352 @@ /* tslint:disable */ import { DocumentNode } from 'graphql'; import gql from 'graphql-tag'; +export type Maybe = T | null; +export type InputMaybe = Maybe; +export type Exact = { [K in keyof T]: T[K] }; +export type MakeOptional = Omit & { [SubKey in K]?: Maybe }; +export type MakeMaybe = Omit & { [SubKey in K]: Maybe }; +export type MakeEmpty = { [_ in K]?: never }; +export type Incremental = T | { [P in keyof T]?: P extends ' $fragmentName' | '__typename' ? T[P] : never }; +/** All built-in and custom scalars, mapped to their actual values */ +export type Scalars = { + ID: { input: string; output: string; } + String: { input: string; output: string; } + Boolean: { input: boolean; output: boolean; } + Int: { input: number; output: number; } + Float: { input: number; output: number; } + DateTime: { input: any; output: any; } + JSON: { input: any; output: any; } + _Any: { input: any; output: any; } + federation__FieldSet: { input: any; output: any; } + link__Import: { input: any; output: any; } +}; + +/** Input type for accepting an invite */ +export type AcceptInviteModel = { + /** The email address of the user accepting the invite */ + email: Scalars['String']['input']; + /** The full name of the user accepting the invite */ + fullname: Scalars['String']['input']; + /** The invite code that was included in the invite email */ + inviteCode: Scalars['String']['input']; + /** The password for the new user account */ + password: Scalars['String']['input']; + /** The ID of the project the invite is associated with */ + projectId: Scalars['String']['input']; +}; + +export type AccessToken = { + __typename?: 'AccessToken'; + accessToken: Scalars['String']['output']; + refreshToken: Scalars['String']['output']; +}; + +export type ConfigurableProjectSettings = { + description?: InputMaybe; + homePage?: InputMaybe; + logo?: InputMaybe; + muiTheme?: InputMaybe; + name?: InputMaybe; + redirectUrl?: InputMaybe; +}; + +export type EmailLoginDto = { + email: Scalars['String']['input']; + password: Scalars['String']['input']; + projectId: Scalars['String']['input']; +}; + +export type ForgotDto = { + email: Scalars['String']['input']; + projectId: Scalars['String']['input']; +}; + +export type GoogleLoginDto = { + credential: Scalars['String']['input']; + projectId: Scalars['String']['input']; +}; + +export type InviteModel = { + __typename?: 'InviteModel'; + /** The date and time at which the invitation was created. */ + createdAt: Scalars['DateTime']['output']; + /** The date and time at which the invitation was deleted, if applicable. */ + deletedAt?: Maybe; + /** The email address of the user being invited. */ + email: Scalars['String']['output']; + /** The date and time at which the invitation expires. */ + expiresAt: Scalars['DateTime']['output']; + /** The ID of the invitation. */ + id: Scalars['ID']['output']; + /** The ID of the project to which the invitation belongs. */ + projectId: Scalars['String']['output']; + /** The role that the user being invited will have. */ + role: Scalars['Int']['output']; + /** The status of the invitation. */ + status: InviteStatus; + /** The date and time at which the invitation was last updated. */ + updatedAt: Scalars['DateTime']['output']; +}; + +/** The status of an invite */ +export enum InviteStatus { + Accepted = 'ACCEPTED', + Cancelled = 'CANCELLED', + Expired = 'EXPIRED', + Pending = 'PENDING' +} + +export type Mutation = { + __typename?: 'Mutation'; + acceptInvite: InviteModel; + cancelInvite: InviteModel; + createInvite: InviteModel; + createProject: ProjectModel; + forgotPassword: Scalars['Boolean']['output']; + loginEmail: AccessToken; + loginGoogle: AccessToken; + loginUsername: AccessToken; + refresh: AccessToken; + resendInvite: InviteModel; + resetPassword: Scalars['Boolean']['output']; + signup: AccessToken; + updateProject: ProjectModel; + updateProjectAuthMethods: ProjectModel; + updateProjectSettings: ProjectModel; + updateUser: UserModel; +}; + + +export type MutationAcceptInviteArgs = { + input: AcceptInviteModel; +}; + + +export type MutationCancelInviteArgs = { + id: Scalars['ID']['input']; +}; + + +export type MutationCreateInviteArgs = { + email: Scalars['String']['input']; + role?: InputMaybe; +}; + + +export type MutationCreateProjectArgs = { + project: ProjectCreateInput; +}; + + +export type MutationForgotPasswordArgs = { + user: ForgotDto; +}; + + +export type MutationLoginEmailArgs = { + user: EmailLoginDto; +}; + + +export type MutationLoginGoogleArgs = { + user: GoogleLoginDto; +}; + + +export type MutationLoginUsernameArgs = { + user: UsernameLoginDto; +}; + + +export type MutationRefreshArgs = { + refreshToken: Scalars['String']['input']; +}; + + +export type MutationResendInviteArgs = { + id: Scalars['ID']['input']; +}; + + +export type MutationResetPasswordArgs = { + user: ResetDto; +}; + + +export type MutationSignupArgs = { + user: UserSignupDto; +}; + + +export type MutationUpdateProjectArgs = { + id: Scalars['String']['input']; + settings: ConfigurableProjectSettings; +}; + + +export type MutationUpdateProjectAuthMethodsArgs = { + id: Scalars['String']['input']; + projectAuthMethods: ProjectAuthMethodsInput; +}; + + +export type MutationUpdateProjectSettingsArgs = { + id: Scalars['String']['input']; + projectSettings: ProjectSettingsInput; +}; + + +export type MutationUpdateUserArgs = { + email: Scalars['String']['input']; + fullname: Scalars['String']['input']; +}; + +export type ProjectAuthMethodsInput = { + emailAuth?: InputMaybe; + googleAuth?: InputMaybe; +}; + +export type ProjectAuthMethodsModel = { + __typename?: 'ProjectAuthMethodsModel'; + emailAuth: Scalars['Boolean']['output']; + googleAuth: Scalars['Boolean']['output']; +}; + +export type ProjectCreateInput = { + allowSignup?: InputMaybe; + description?: InputMaybe; + displayProjectName?: InputMaybe; + emailAuth?: InputMaybe; + googleAuth?: InputMaybe; + homePage?: InputMaybe; + logo?: InputMaybe; + muiTheme?: InputMaybe; + name: Scalars['String']['input']; + redirectUrl?: InputMaybe; +}; + +export type ProjectModel = { + __typename?: 'ProjectModel'; + authMethods: ProjectAuthMethodsModel; + createdAt: Scalars['DateTime']['output']; + deletedAt?: Maybe; + description?: Maybe; + homePage?: Maybe; + id: Scalars['ID']['output']; + logo?: Maybe; + muiTheme: Scalars['JSON']['output']; + name: Scalars['String']['output']; + redirectUrl?: Maybe; + settings: ProjectSettingsModel; + updatedAt: Scalars['DateTime']['output']; + users: Array; +}; + +export type ProjectSettingsInput = { + allowSignup?: InputMaybe; + displayProjectName?: InputMaybe; +}; + +export type ProjectSettingsModel = { + __typename?: 'ProjectSettingsModel'; + allowSignup: Scalars['Boolean']['output']; + displayProjectName: Scalars['Boolean']['output']; +}; + +export type Query = { + __typename?: 'Query'; + _entities: Array>; + _service: _Service; + getProject: ProjectModel; + getUser: UserModel; + invite: InviteModel; + invites: Array; + listProjects: Array; + me: UserModel; + projectUsers: Array; + publicKey: Array; + users: Array; +}; + + +export type Query_EntitiesArgs = { + representations: Array; +}; + + +export type QueryGetProjectArgs = { + id: Scalars['String']['input']; +}; + + +export type QueryGetUserArgs = { + id: Scalars['ID']['input']; +}; + + +export type QueryInviteArgs = { + id: Scalars['ID']['input']; +}; + + +export type QueryInvitesArgs = { + status?: InputMaybe; +}; + + +export type QueryProjectUsersArgs = { + projectId: Scalars['String']['input']; +}; + +export type ResetDto = { + code: Scalars['String']['input']; + email: Scalars['String']['input']; + password: Scalars['String']['input']; + projectId: Scalars['String']['input']; +}; + +export type UserModel = { + __typename?: 'UserModel'; + createdAt: Scalars['DateTime']['output']; + deletedAt?: Maybe; + email?: Maybe; + fullname?: Maybe; + id: Scalars['ID']['output']; + projectId: Scalars['String']['output']; + role: Scalars['Int']['output']; + updatedAt: Scalars['DateTime']['output']; + username?: Maybe; +}; + +export type UserSignupDto = { + email: Scalars['String']['input']; + fullname: Scalars['String']['input']; + password: Scalars['String']['input']; + projectId: Scalars['String']['input']; + username?: InputMaybe; +}; + +export type UsernameLoginDto = { + password: Scalars['String']['input']; + projectId: Scalars['String']['input']; + username: Scalars['String']['input']; +}; + +export type _Entity = InviteModel | ProjectModel | UserModel; + +export type _Service = { + __typename?: '_Service'; + sdl?: Maybe; +}; + +export enum Link__Purpose { + /** `EXECUTION` features provide metadata necessary for operation execution. */ + Execution = 'EXECUTION', + /** `SECURITY` features provide metadata necessary to securely resolve fields. */ + Security = 'SECURITY' +} + export type ProjectUsersQueryVariables = Exact<{ projectId: Scalars['String']['input']; }>; diff --git a/packages/server/src/auth/providers/sdk.provider.ts b/packages/server/src/auth/providers/sdk.provider.ts new file mode 100644 index 00000000..c14a35e8 --- /dev/null +++ b/packages/server/src/auth/providers/sdk.provider.ts @@ -0,0 +1,12 @@ +import { Provider } from '@nestjs/common'; +import { Sdk } from '../graphql/sdk'; + +export const AUTH_SDK_PROVIDER = 'AUTH_SDK_PROVIDER'; + +export const authSDKProvider: Provider = { + provide: AUTH_SDK_PROVIDER, + useFactory: async () => { + + + } +}; diff --git a/packages/server/src/user/user.module.ts b/packages/server/src/user/user.module.ts deleted file mode 100644 index 8a961e24..00000000 --- a/packages/server/src/user/user.module.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { Module } from '@nestjs/common'; -import { UserService } from './user.service'; - -@Module({ - providers: [UserService], - exports: [UserService] -}) -export class UserModule {} diff --git a/packages/server/src/user/user.service.ts b/packages/server/src/user/user.service.ts deleted file mode 100644 index 81b978ae..00000000 --- a/packages/server/src/user/user.service.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { Injectable } from '@nestjs/common'; -import { sdk } from '../graphql/sdk'; - -@Injectable() -export class UserService { -} From aa43c5f2e13874e27cc8b0ca1eef8c7c2381e6e0 Mon Sep 17 00:00:00 2001 From: cbolles Date: Thu, 4 Jan 2024 16:25:44 -0500 Subject: [PATCH 3/5] Working provider for GraphQL SDK on Auth microservice --- package-lock.json | 45 +++++++++---------- packages/server/package.json | 3 +- packages/server/schema.gql | 1 + packages/server/src/auth/auth.module.ts | 7 ++- packages/server/src/auth/graphql-codegen.yml | 2 +- packages/server/src/auth/graphql/sdk.ts | 16 ++++--- .../server/src/auth/providers/sdk.provider.ts | 13 ++++-- .../server/src/auth/services/user.service.ts | 15 +++++++ packages/server/src/config/configuration.ts | 3 +- .../src/permission/permission.module.ts | 5 ++- .../src/permission/permission.resolver.ts | 13 +++++- .../src/permission/permission.service.ts | 14 +++++- packages/server/src/project/project.module.ts | 4 +- 13 files changed, 101 insertions(+), 40 deletions(-) create mode 100644 packages/server/src/auth/services/user.service.ts diff --git a/package-lock.json b/package-lock.json index 47f77882..8cae7a19 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4348,10 +4348,10 @@ "graphql": "^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0" } }, - "node_modules/@graphql-codegen/typescript-generic-sdk": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@graphql-codegen/typescript-generic-sdk/-/typescript-generic-sdk-4.0.0.tgz", - "integrity": "sha512-5tBHoIEEqvF5JVJpvyIGF9/zRNPYGJJU3hT9OWHBE759Fj0Q48O4BhZfBABtK64R/R0iWBmZWmt0HKIV/6b0Xg==", + "node_modules/@graphql-codegen/typescript-graphql-request": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@graphql-codegen/typescript-graphql-request/-/typescript-graphql-request-6.1.0.tgz", + "integrity": "sha512-hMhBazvdSQWuOrH6GnYew8zb9wDq9le0o3tPu07if/v97LVVKsVfPNcMG/cTLYZufaog9o2jScLbyo17HEqicg==", "dev": true, "dependencies": { "@graphql-codegen/plugin-helpers": "^3.0.0", @@ -4364,10 +4364,11 @@ }, "peerDependencies": { "graphql": "^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0", + "graphql-request": "^6.0.0", "graphql-tag": "^2.0.0" } }, - "node_modules/@graphql-codegen/typescript-generic-sdk/node_modules/@graphql-codegen/plugin-helpers": { + "node_modules/@graphql-codegen/typescript-graphql-request/node_modules/@graphql-codegen/plugin-helpers": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/@graphql-codegen/plugin-helpers/-/plugin-helpers-3.1.2.tgz", "integrity": "sha512-emOQiHyIliVOIjKVKdsI5MXj312zmRDwmHpyUTZMjfpvxq/UVAHUJIVdVf+lnjjrI+LXBTgMlTWTgHQfmICxjg==", @@ -4384,13 +4385,13 @@ "graphql": "^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0" } }, - "node_modules/@graphql-codegen/typescript-generic-sdk/node_modules/@graphql-codegen/plugin-helpers/node_modules/tslib": { + "node_modules/@graphql-codegen/typescript-graphql-request/node_modules/@graphql-codegen/plugin-helpers/node_modules/tslib": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==", "dev": true }, - "node_modules/@graphql-codegen/typescript-generic-sdk/node_modules/@graphql-codegen/visitor-plugin-common": { + "node_modules/@graphql-codegen/typescript-graphql-request/node_modules/@graphql-codegen/visitor-plugin-common": { "version": "2.13.1", "resolved": "https://registry.npmjs.org/@graphql-codegen/visitor-plugin-common/-/visitor-plugin-common-2.13.1.tgz", "integrity": "sha512-mD9ufZhDGhyrSaWQGrU1Q1c5f01TeWtSWy/cDwXYjJcHIj1Y/DG2x0tOflEfCvh5WcnmHNIw4lzDsg1W7iFJEg==", @@ -4411,7 +4412,7 @@ "graphql": "^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0" } }, - "node_modules/@graphql-codegen/typescript-generic-sdk/node_modules/@graphql-codegen/visitor-plugin-common/node_modules/@graphql-codegen/plugin-helpers": { + "node_modules/@graphql-codegen/typescript-graphql-request/node_modules/@graphql-codegen/visitor-plugin-common/node_modules/@graphql-codegen/plugin-helpers": { "version": "2.7.2", "resolved": "https://registry.npmjs.org/@graphql-codegen/plugin-helpers/-/plugin-helpers-2.7.2.tgz", "integrity": "sha512-kln2AZ12uii6U59OQXdjLk5nOlh1pHis1R98cDZGFnfaiAbX9V3fxcZ1MMJkB7qFUymTALzyjZoXXdyVmPMfRg==", @@ -4428,7 +4429,7 @@ "graphql": "^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0" } }, - "node_modules/@graphql-codegen/typescript-generic-sdk/node_modules/@graphql-codegen/visitor-plugin-common/node_modules/@graphql-tools/utils": { + "node_modules/@graphql-codegen/typescript-graphql-request/node_modules/@graphql-codegen/visitor-plugin-common/node_modules/@graphql-tools/utils": { "version": "8.13.1", "resolved": "https://registry.npmjs.org/@graphql-tools/utils/-/utils-8.13.1.tgz", "integrity": "sha512-qIh9yYpdUFmctVqovwMdheVNJqFh+DQNWIhX87FJStfXYnmweBUDATok9fWPleKeFwxnW8IapKmY8m8toJEkAw==", @@ -4440,7 +4441,7 @@ "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" } }, - "node_modules/@graphql-codegen/typescript-generic-sdk/node_modules/@graphql-codegen/visitor-plugin-common/node_modules/change-case-all": { + "node_modules/@graphql-codegen/typescript-graphql-request/node_modules/@graphql-codegen/visitor-plugin-common/node_modules/change-case-all": { "version": "1.0.14", "resolved": "https://registry.npmjs.org/change-case-all/-/change-case-all-1.0.14.tgz", "integrity": "sha512-CWVm2uT7dmSHdO/z1CXT/n47mWonyypzBbuCy5tN7uMg22BsfkhwT6oHmFCAk+gL1LOOxhdbB9SZz3J1KTY3gA==", @@ -4458,13 +4459,13 @@ "upper-case-first": "^2.0.2" } }, - "node_modules/@graphql-codegen/typescript-generic-sdk/node_modules/@graphql-codegen/visitor-plugin-common/node_modules/tslib": { + "node_modules/@graphql-codegen/typescript-graphql-request/node_modules/@graphql-codegen/visitor-plugin-common/node_modules/tslib": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==", "dev": true }, - "node_modules/@graphql-codegen/typescript-generic-sdk/node_modules/@graphql-tools/optimize": { + "node_modules/@graphql-codegen/typescript-graphql-request/node_modules/@graphql-tools/optimize": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/@graphql-tools/optimize/-/optimize-1.4.0.tgz", "integrity": "sha512-dJs/2XvZp+wgHH8T5J2TqptT9/6uVzIYvA6uFACha+ufvdMBedkfR4b4GbT8jAKLRARiqRTxy3dctnwkTM2tdw==", @@ -4476,7 +4477,7 @@ "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" } }, - "node_modules/@graphql-codegen/typescript-generic-sdk/node_modules/@graphql-tools/relay-operation-optimizer": { + "node_modules/@graphql-codegen/typescript-graphql-request/node_modules/@graphql-tools/relay-operation-optimizer": { "version": "6.5.18", "resolved": "https://registry.npmjs.org/@graphql-tools/relay-operation-optimizer/-/relay-operation-optimizer-6.5.18.tgz", "integrity": "sha512-mc5VPyTeV+LwiM+DNvoDQfPqwQYhPV/cl5jOBjTgSniyaq8/86aODfMkrE2OduhQ5E00hqrkuL2Fdrgk0w1QJg==", @@ -14540,7 +14541,6 @@ "version": "3.1.8", "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.8.tgz", "integrity": "sha512-cvA+JwZoU0Xq+h6WkMvAUqPEYy92Obet6UdKLfW60qn99ftItKjB5T+BkyWOFWe2pUyfQ+IJHmpOTznqk1M6Kg==", - "dev": true, "dependencies": { "node-fetch": "^2.6.12" } @@ -16969,7 +16969,6 @@ "version": "6.1.0", "resolved": "https://registry.npmjs.org/graphql-request/-/graphql-request-6.1.0.tgz", "integrity": "sha512-p+XPfS4q7aIpKVcgmnZKhMNqhltk20hfXtkaIkTfjjmiKMJ5xrt5c743cL03y/K7y1rg3WrIC49xGiEQ4mxdNw==", - "dev": true, "dependencies": { "@graphql-typed-document-node/core": "^3.2.0", "cross-fetch": "^3.1.5" @@ -26521,6 +26520,7 @@ "casbin": "^5.28.0", "casbin-mongoose-adapter": "^5.3.1", "csv-parser": "^3.0.0", + "graphql-request": "^6.1.0", "graphql-type-json": "^0.3.2", "jsonschema": "^1.4.1", "mongoose": "^7.4.3", @@ -26530,7 +26530,7 @@ "rxjs": "^7.2.0" }, "devDependencies": { - "@graphql-codegen/typescript-generic-sdk": "^4.0.0", + "@graphql-codegen/typescript-graphql-request": "^6.1.0", "@nestjs/cli": "^9.0.0", "@nestjs/schematics": "^9.0.0", "@nestjs/testing": "^9.0.0", @@ -29629,10 +29629,10 @@ } } }, - "@graphql-codegen/typescript-generic-sdk": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@graphql-codegen/typescript-generic-sdk/-/typescript-generic-sdk-4.0.0.tgz", - "integrity": "sha512-5tBHoIEEqvF5JVJpvyIGF9/zRNPYGJJU3hT9OWHBE759Fj0Q48O4BhZfBABtK64R/R0iWBmZWmt0HKIV/6b0Xg==", + "@graphql-codegen/typescript-graphql-request": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@graphql-codegen/typescript-graphql-request/-/typescript-graphql-request-6.1.0.tgz", + "integrity": "sha512-hMhBazvdSQWuOrH6GnYew8zb9wDq9le0o3tPu07if/v97LVVKsVfPNcMG/cTLYZufaog9o2jScLbyo17HEqicg==", "dev": true, "requires": { "@graphql-codegen/plugin-helpers": "^3.0.0", @@ -36981,7 +36981,6 @@ "version": "3.1.8", "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.8.tgz", "integrity": "sha512-cvA+JwZoU0Xq+h6WkMvAUqPEYy92Obet6UdKLfW60qn99ftItKjB5T+BkyWOFWe2pUyfQ+IJHmpOTznqk1M6Kg==", - "dev": true, "requires": { "node-fetch": "^2.6.12" } @@ -38842,7 +38841,6 @@ "version": "6.1.0", "resolved": "https://registry.npmjs.org/graphql-request/-/graphql-request-6.1.0.tgz", "integrity": "sha512-p+XPfS4q7aIpKVcgmnZKhMNqhltk20hfXtkaIkTfjjmiKMJ5xrt5c743cL03y/K7y1rg3WrIC49xGiEQ4mxdNw==", - "dev": true, "requires": { "@graphql-typed-document-node/core": "^3.2.0", "cross-fetch": "^3.1.5" @@ -43418,7 +43416,7 @@ "requires": { "@apollo/subgraph": "^2.4.12", "@google-cloud/storage": "^7.7.0", - "@graphql-codegen/typescript-generic-sdk": "*", + "@graphql-codegen/typescript-graphql-request": "^6.1.0", "@nestjs/apollo": "^12.0.7", "@nestjs/axios": "^3.0.1", "@nestjs/cli": "^9.0.0", @@ -43445,6 +43443,7 @@ "eslint": "^8.0.1", "eslint-config-prettier": "^8.3.0", "eslint-plugin-prettier": "^4.0.0", + "graphql-request": "*", "graphql-type-json": "^0.3.2", "jest": "28.1.3", "jsonschema": "^1.4.1", diff --git a/packages/server/package.json b/packages/server/package.json index 8e9d74f7..011457c0 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -39,6 +39,7 @@ "casbin": "^5.28.0", "casbin-mongoose-adapter": "^5.3.1", "csv-parser": "^3.0.0", + "graphql-request": "^6.1.0", "graphql-type-json": "^0.3.2", "jsonschema": "^1.4.1", "mongoose": "^7.4.3", @@ -48,7 +49,7 @@ "rxjs": "^7.2.0" }, "devDependencies": { - "@graphql-codegen/typescript-generic-sdk": "^4.0.0", + "@graphql-codegen/typescript-graphql-request": "^6.1.0", "@nestjs/cli": "^9.0.0", "@nestjs/schematics": "^9.0.0", "@nestjs/testing": "^9.0.0", diff --git a/packages/server/schema.gql b/packages/server/schema.gql index 0f06f13e..2ece492a 100644 --- a/packages/server/schema.gql +++ b/packages/server/schema.gql @@ -108,6 +108,7 @@ type Query { getOrganizations: [Organization!]! exists(name: String!): Boolean! getDatasets: [Dataset!]! + getProjectPermissions(project: ID!): Boolean! projectExists(name: String!): Boolean! getProjects: [Project!]! studyExists(name: String!, project: ID!): Boolean! diff --git a/packages/server/src/auth/auth.module.ts b/packages/server/src/auth/auth.module.ts index 7459c064..1a85795b 100644 --- a/packages/server/src/auth/auth.module.ts +++ b/packages/server/src/auth/auth.module.ts @@ -1,4 +1,9 @@ import { Module } from '@nestjs/common'; +import { authSDKProvider } from './providers/sdk.provider'; +import { UserService } from './services/user.service'; -@Module({}) +@Module({ + providers: [authSDKProvider, UserService], + exports: [UserService] +}) export class AuthModule {} diff --git a/packages/server/src/auth/graphql-codegen.yml b/packages/server/src/auth/graphql-codegen.yml index dc1fda2e..68e54850 100644 --- a/packages/server/src/auth/graphql-codegen.yml +++ b/packages/server/src/auth/graphql-codegen.yml @@ -21,4 +21,4 @@ generates: content: '/* tslint:disable */' - typescript - typescript-operations - - typescript-generic-sdk + - typescript-graphql-request diff --git a/packages/server/src/auth/graphql/sdk.ts b/packages/server/src/auth/graphql/sdk.ts index 621989be..21e86e56 100644 --- a/packages/server/src/auth/graphql/sdk.ts +++ b/packages/server/src/auth/graphql/sdk.ts @@ -1,6 +1,7 @@ /* Generated File DO NOT EDIT. */ /* tslint:disable */ -import { DocumentNode } from 'graphql'; +import { GraphQLClient } from 'graphql-request'; +import { GraphQLClientRequestHeaders } from 'graphql-request/build/cjs/types'; import gql from 'graphql-tag'; export type Maybe = T | null; export type InputMaybe = Maybe; @@ -371,11 +372,16 @@ export const ProjectUsersDocument = gql` } } `; -export type Requester = (doc: DocumentNode, vars?: V, options?: C) => Promise | AsyncIterable -export function getSdk(requester: Requester) { + +export type SdkFunctionWrapper = (action: (requestHeaders?:Record) => Promise, operationName: string, operationType?: string, variables?: any) => Promise; + + +const defaultWrapper: SdkFunctionWrapper = (action, _operationName, _operationType, variables) => action(); + +export function getSdk(client: GraphQLClient, withWrapper: SdkFunctionWrapper = defaultWrapper) { return { - projectUsers(variables: ProjectUsersQueryVariables, options?: C): Promise { - return requester(ProjectUsersDocument, variables, options) as Promise; + projectUsers(variables: ProjectUsersQueryVariables, requestHeaders?: GraphQLClientRequestHeaders): Promise { + return withWrapper((wrappedRequestHeaders) => client.request(ProjectUsersDocument, variables, {...requestHeaders, ...wrappedRequestHeaders}), 'projectUsers', 'query', variables); } }; } diff --git a/packages/server/src/auth/providers/sdk.provider.ts b/packages/server/src/auth/providers/sdk.provider.ts index c14a35e8..012e7267 100644 --- a/packages/server/src/auth/providers/sdk.provider.ts +++ b/packages/server/src/auth/providers/sdk.provider.ts @@ -1,12 +1,19 @@ import { Provider } from '@nestjs/common'; -import { Sdk } from '../graphql/sdk'; +import { Sdk, getSdk } from '../graphql/sdk'; +import { GraphQLClient } from 'graphql-request'; +import { ConfigService } from '@nestjs/config'; export const AUTH_SDK_PROVIDER = 'AUTH_SDK_PROVIDER'; export const authSDKProvider: Provider = { provide: AUTH_SDK_PROVIDER, - useFactory: async () => { - + inject: [ConfigService], + useFactory: async (configService: ConfigService) => { + // TODO: In the future, authentication will need to be handled on + // the endpoint + const endpoint = configService.getOrThrow('auth.graphqlEndpoint'); + const client = new GraphQLClient(endpoint); + return getSdk(client); } }; diff --git a/packages/server/src/auth/services/user.service.ts b/packages/server/src/auth/services/user.service.ts new file mode 100644 index 00000000..62fbe9f2 --- /dev/null +++ b/packages/server/src/auth/services/user.service.ts @@ -0,0 +1,15 @@ +import { Inject, Injectable } from '@nestjs/common'; +import { AUTH_SDK_PROVIDER } from '../providers/sdk.provider'; +import { Sdk, UserModel } from '../graphql/sdk'; + + +@Injectable() +export class UserService { + constructor(@Inject(AUTH_SDK_PROVIDER) private readonly authSdk: Sdk) {} + + async getUsersForProject(projectId: string): Promise { + const { projectUsers } = await this.authSdk.projectUsers({ projectId }); + + return projectUsers; + } +} diff --git a/packages/server/src/config/configuration.ts b/packages/server/src/config/configuration.ts index 9cf2592d..59598b66 100644 --- a/packages/server/src/config/configuration.ts +++ b/packages/server/src/config/configuration.ts @@ -20,7 +20,8 @@ export default () => ({ signedURLExpiration: process.env.GCP_STORAGE_ENTRY_SIGNED_URL_EXPIRATION || 15 * 60 * 1000 // 15 minutes }, auth: { - publicKeyUrl: process.env.AUTH_PUBLIC_KEY_URL || 'https://test-auth-service.sail.codes/public-key' + publicKeyUrl: process.env.AUTH_PUBLIC_KEY_URL || 'https://test-auth-service.sail.codes/public-key', + graphqlEndpoint: process.env.AUTH_GRAPHQL_ENDPOINT || 'https://test-auth-service.sail.codes/graphql' }, casbin: { model: process.env.CASBIN_MODEL || 'src/config/casbin-model.conf', diff --git a/packages/server/src/permission/permission.module.ts b/packages/server/src/permission/permission.module.ts index 534e61f0..950bee2b 100644 --- a/packages/server/src/permission/permission.module.ts +++ b/packages/server/src/permission/permission.module.ts @@ -1,9 +1,12 @@ -import { Module } from '@nestjs/common'; +import { Module, forwardRef } from '@nestjs/common'; import { casbinProvider } from './casbin.provider'; import { PermissionResolver } from './permission.resolver'; import { PermissionService } from './permission.service'; +import { ProjectModule } from '../project/project.module'; +import { AuthModule } from '../auth/auth.module'; @Module({ + imports: [forwardRef(() => ProjectModule), AuthModule], providers: [casbinProvider, PermissionResolver, PermissionService], exports: [casbinProvider] }) diff --git a/packages/server/src/permission/permission.resolver.ts b/packages/server/src/permission/permission.resolver.ts index a3b7692c..985db479 100644 --- a/packages/server/src/permission/permission.resolver.ts +++ b/packages/server/src/permission/permission.resolver.ts @@ -1,4 +1,4 @@ -import { Resolver, Mutation, Args, ID } from '@nestjs/graphql'; +import { Resolver, Mutation, Args, ID, Query } from '@nestjs/graphql'; import { JwtAuthGuard } from '../jwt/jwt.guard'; import { UseGuards } from '@nestjs/common'; import { TokenContext } from '../jwt/token.context'; @@ -6,6 +6,8 @@ import { TokenPayload } from '../jwt/token.dto'; import { PermissionService } from './permission.service'; import { OrganizationContext } from 'src/organization/organization.context'; import { Organization } from 'src/organization/organization.model'; +import { ProjectPipe } from '../project/pipes/project.pipe'; +import { Project } from '../project/project.model'; @UseGuards(JwtAuthGuard) @Resolver() @@ -21,4 +23,13 @@ export class PermissionResolver { await this.permissionService.grantOwner(targetUser, requestingUser.id, organization._id); return true; } + + @Query(() => Boolean) + async getProjectPermissions( + @Args('project', { type: () => ID }, ProjectPipe) project: Project, + @TokenContext() requestingUser: TokenPayload + ): Promise { + return this.permissionService.getProjectPermissions(project, requestingUser); + } + } diff --git a/packages/server/src/permission/permission.service.ts b/packages/server/src/permission/permission.service.ts index d07f8cbd..91cbdc1a 100644 --- a/packages/server/src/permission/permission.service.ts +++ b/packages/server/src/permission/permission.service.ts @@ -2,10 +2,14 @@ import { Injectable, Inject, UnauthorizedException } from '@nestjs/common'; import { CASBIN_PROVIDER } from './casbin.provider'; import * as casbin from 'casbin'; import { Roles } from './permissions/roles'; +import { UserService } from '../auth/services/user.service'; +import {Project} from 'src/project/project.model'; +import {TokenPayload} from 'src/jwt/token.dto'; @Injectable() export class PermissionService { - constructor(@Inject(CASBIN_PROVIDER) private readonly enforcer: casbin.Enforcer) {} + constructor(@Inject(CASBIN_PROVIDER) private readonly enforcer: casbin.Enforcer, + private readonly userService: UserService) {} /** requestingUser must be an owner themselves */ async grantOwner(targetUser: string, requestingUser: string, organization: string): Promise { @@ -17,4 +21,12 @@ export class PermissionService { await this.enforcer.addPolicy(targetUser, Roles.OWNER, organization); } + + async getProjectPermissions(project: Project, requestingUser: TokenPayload): Promise { + // Get all the users associated with the organization + const users = await this.userService.getUsersForProject(requestingUser.projectId); + console.log(users); + + return true; + } } diff --git a/packages/server/src/project/project.module.ts b/packages/server/src/project/project.module.ts index 61a6c339..fa584f5d 100644 --- a/packages/server/src/project/project.module.ts +++ b/packages/server/src/project/project.module.ts @@ -1,4 +1,4 @@ -import { Module } from '@nestjs/common'; +import { Module, forwardRef } from '@nestjs/common'; import { ProjectResolver } from './project.resolver'; import { ProjectService } from './project.service'; import { MongooseModule } from '@nestjs/mongoose'; @@ -29,7 +29,7 @@ import { PermissionModule } from '../permission/permission.module'; } ]), JwtModule, - PermissionModule + forwardRef(() => PermissionModule) ], providers: [ProjectResolver, ProjectService, ProjectPipe], exports: [ProjectPipe, ProjectService] From 0e1d2a840bfcaabc502d4bf6ad8a7cb8f58abe73 Mon Sep 17 00:00:00 2001 From: cbolles Date: Fri, 5 Jan 2024 10:35:40 -0500 Subject: [PATCH 4/5] Working auth integration --- packages/server/schema.gql | 20 +++++++++++- .../server/src/auth/services/user.service.ts | 2 -- .../server/src/permission/permission.model.ts | 31 +++++++++++++++++++ .../src/permission/permission.resolver.ts | 13 +++++--- .../src/permission/permission.service.ts | 23 +++++++++++--- .../src/permission/permissions/project.ts | 4 ++- 6 files changed, 80 insertions(+), 13 deletions(-) create mode 100644 packages/server/src/permission/permission.model.ts diff --git a/packages/server/schema.gql b/packages/server/schema.gql index 2ece492a..1bbaeea9 100644 --- a/packages/server/schema.gql +++ b/packages/server/schema.gql @@ -28,6 +28,24 @@ A date-time string at UTC, such as 2019-12-03T09:54:33Z, compliant with the date """ scalar DateTime +type UserModel { + id: ID! +} + +type Permission { + user: UserModel! + role: Roles! + hasRole: Boolean! + editable: Boolean! +} + +enum Roles { + OWNER + PROJECT_ADMIN + STUDY_ADMIN + CONTRIBUTOR +} + type TagSchema { dataSchema: JSON! uiSchema: JSON! @@ -108,7 +126,7 @@ type Query { getOrganizations: [Organization!]! exists(name: String!): Boolean! getDatasets: [Dataset!]! - getProjectPermissions(project: ID!): Boolean! + getProjectPermissions(project: ID!): [Permission!]! projectExists(name: String!): Boolean! getProjects: [Project!]! studyExists(name: String!, project: ID!): Boolean! diff --git a/packages/server/src/auth/services/user.service.ts b/packages/server/src/auth/services/user.service.ts index 62fbe9f2..6823ed85 100644 --- a/packages/server/src/auth/services/user.service.ts +++ b/packages/server/src/auth/services/user.service.ts @@ -2,14 +2,12 @@ import { Inject, Injectable } from '@nestjs/common'; import { AUTH_SDK_PROVIDER } from '../providers/sdk.provider'; import { Sdk, UserModel } from '../graphql/sdk'; - @Injectable() export class UserService { constructor(@Inject(AUTH_SDK_PROVIDER) private readonly authSdk: Sdk) {} async getUsersForProject(projectId: string): Promise { const { projectUsers } = await this.authSdk.projectUsers({ projectId }); - return projectUsers; } } diff --git a/packages/server/src/permission/permission.model.ts b/packages/server/src/permission/permission.model.ts new file mode 100644 index 00000000..1b3eff1c --- /dev/null +++ b/packages/server/src/permission/permission.model.ts @@ -0,0 +1,31 @@ +import { ObjectType, registerEnumType, Field, Directive, ID } from '@nestjs/graphql'; +import { Roles } from './permissions/roles'; + +registerEnumType(Roles, { + name: 'Roles' +}); + +/** Definition for external user */ +@ObjectType() +@Directive('@key(fields: "id")') +@Directive('@extends') +export class UserModel { + @Field(() => ID) + @Directive('@external') + id: string; +} + +@ObjectType() +export class Permission { + @Field(() => UserModel) + user: string; + + @Field(() => Roles) + role: Roles; + + @Field() + hasRole: boolean; + + @Field() + editable: boolean; +} diff --git a/packages/server/src/permission/permission.resolver.ts b/packages/server/src/permission/permission.resolver.ts index 985db479..7204f3ae 100644 --- a/packages/server/src/permission/permission.resolver.ts +++ b/packages/server/src/permission/permission.resolver.ts @@ -1,4 +1,4 @@ -import { Resolver, Mutation, Args, ID, Query } from '@nestjs/graphql'; +import { Resolver, Mutation, Args, ID, Query, ResolveField, Parent } from '@nestjs/graphql'; import { JwtAuthGuard } from '../jwt/jwt.guard'; import { UseGuards } from '@nestjs/common'; import { TokenContext } from '../jwt/token.context'; @@ -8,9 +8,10 @@ import { OrganizationContext } from 'src/organization/organization.context'; import { Organization } from 'src/organization/organization.model'; import { ProjectPipe } from '../project/pipes/project.pipe'; import { Project } from '../project/project.model'; +import { Permission, UserModel } from './permission.model'; @UseGuards(JwtAuthGuard) -@Resolver() +@Resolver(() => Permission) export class PermissionResolver { constructor(private readonly permissionService: PermissionService) {} @@ -24,12 +25,16 @@ export class PermissionResolver { return true; } - @Query(() => Boolean) + @Query(() => [Permission]) async getProjectPermissions( @Args('project', { type: () => ID }, ProjectPipe) project: Project, @TokenContext() requestingUser: TokenPayload - ): Promise { + ): Promise { return this.permissionService.getProjectPermissions(project, requestingUser); } + @ResolveField('user', () => UserModel) + resolveUser(@Parent() permission: Permission): any { + return { __typename: 'UserModel', id: permission.user }; + } } diff --git a/packages/server/src/permission/permission.service.ts b/packages/server/src/permission/permission.service.ts index 91cbdc1a..48d185bc 100644 --- a/packages/server/src/permission/permission.service.ts +++ b/packages/server/src/permission/permission.service.ts @@ -3,8 +3,9 @@ import { CASBIN_PROVIDER } from './casbin.provider'; import * as casbin from 'casbin'; import { Roles } from './permissions/roles'; import { UserService } from '../auth/services/user.service'; -import {Project} from 'src/project/project.model'; -import {TokenPayload} from 'src/jwt/token.dto'; +import { Project } from '../project/project.model'; +import { TokenPayload } from '../jwt/token.dto'; +import { Permission } from './permission.model'; @Injectable() export class PermissionService { @@ -22,11 +23,23 @@ export class PermissionService { await this.enforcer.addPolicy(targetUser, Roles.OWNER, organization); } - async getProjectPermissions(project: Project, requestingUser: TokenPayload): Promise { + async getProjectPermissions(project: Project, requestingUser: TokenPayload): Promise { // Get all the users associated with the organization const users = await this.userService.getUsersForProject(requestingUser.projectId); - console.log(users); - return true; + // Create the cooresponding permission representation + const permissions = await Promise.all(users.map(async user => { + const hasRole = await this.enforcer.enforce(user.id, Roles.PROJECT_ADMIN, project._id); + const editable = !(await this.enforcer.enforce(user.id, Roles.OWNER, project._id)); + + return { + user: user.id, + role: Roles.PROJECT_ADMIN, + hasRole, + editable + }; + })); + + return permissions; } } diff --git a/packages/server/src/permission/permissions/project.ts b/packages/server/src/permission/permissions/project.ts index 0327f6e8..bc4bb5f4 100644 --- a/packages/server/src/permission/permissions/project.ts +++ b/packages/server/src/permission/permissions/project.ts @@ -5,7 +5,8 @@ export enum ProjectPermissions { CREATE = 'project:create', READ = 'project:read', UPDATE = 'project:update', - DELETE = 'project:delete' + DELETE = 'project:delete', + GRANT_ADMIN = 'project:grant_admin', } /** All role to project permissions */ @@ -13,6 +14,7 @@ export const roleToProjectPermissions: string[][] = [ // OWNER permissions [Roles.OWNER, ProjectPermissions.CREATE], [Roles.OWNER, ProjectPermissions.DELETE], + [Roles.OWNER, ProjectPermissions.GRANT_ADMIN], // PROJECT_ADMIN permissions [Roles.PROJECT_ADMIN, ProjectPermissions.UPDATE], From dd727ca18e9c9aceaff45e4bca1612eb5b4d0720 Mon Sep 17 00:00:00 2001 From: cbolles Date: Fri, 5 Jan 2024 10:36:11 -0500 Subject: [PATCH 5/5] Fix formatting --- packages/server/src/auth/graphql/graphql.ts | 42 ++----- packages/server/src/auth/graphql/sdk.ts | 112 +++++++++--------- .../server/src/auth/graphql/users/users.ts | 54 ++++++--- .../src/permission/permission.service.ts | 28 +++-- .../src/permission/permissions/project.ts | 2 +- 5 files changed, 121 insertions(+), 117 deletions(-) diff --git a/packages/server/src/auth/graphql/graphql.ts b/packages/server/src/auth/graphql/graphql.ts index db2208fd..e184b165 100644 --- a/packages/server/src/auth/graphql/graphql.ts +++ b/packages/server/src/auth/graphql/graphql.ts @@ -9,16 +9,16 @@ export type MakeEmpty = export type Incremental = T | { [P in keyof T]?: P extends ' $fragmentName' | '__typename' ? T[P] : never }; /** All built-in and custom scalars, mapped to their actual values */ export type Scalars = { - ID: { input: string; output: string; } - String: { input: string; output: string; } - Boolean: { input: boolean; output: boolean; } - Int: { input: number; output: number; } - Float: { input: number; output: number; } - DateTime: { input: any; output: any; } - JSON: { input: any; output: any; } - _Any: { input: any; output: any; } - federation__FieldSet: { input: any; output: any; } - link__Import: { input: any; output: any; } + ID: { input: string; output: string }; + String: { input: string; output: string }; + Boolean: { input: boolean; output: boolean }; + Int: { input: number; output: number }; + Float: { input: number; output: number }; + DateTime: { input: any; output: any }; + JSON: { input: any; output: any }; + _Any: { input: any; output: any }; + federation__FieldSet: { input: any; output: any }; + link__Import: { input: any; output: any }; }; /** Input type for accepting an invite */ @@ -116,86 +116,70 @@ export type Mutation = { updateUser: UserModel; }; - export type MutationAcceptInviteArgs = { input: AcceptInviteModel; }; - export type MutationCancelInviteArgs = { id: Scalars['ID']['input']; }; - export type MutationCreateInviteArgs = { email: Scalars['String']['input']; role?: InputMaybe; }; - export type MutationCreateProjectArgs = { project: ProjectCreateInput; }; - export type MutationForgotPasswordArgs = { user: ForgotDto; }; - export type MutationLoginEmailArgs = { user: EmailLoginDto; }; - export type MutationLoginGoogleArgs = { user: GoogleLoginDto; }; - export type MutationLoginUsernameArgs = { user: UsernameLoginDto; }; - export type MutationRefreshArgs = { refreshToken: Scalars['String']['input']; }; - export type MutationResendInviteArgs = { id: Scalars['ID']['input']; }; - export type MutationResetPasswordArgs = { user: ResetDto; }; - export type MutationSignupArgs = { user: UserSignupDto; }; - export type MutationUpdateProjectArgs = { id: Scalars['String']['input']; settings: ConfigurableProjectSettings; }; - export type MutationUpdateProjectAuthMethodsArgs = { id: Scalars['String']['input']; projectAuthMethods: ProjectAuthMethodsInput; }; - export type MutationUpdateProjectSettingsArgs = { id: Scalars['String']['input']; projectSettings: ProjectSettingsInput; }; - export type MutationUpdateUserArgs = { email: Scalars['String']['input']; fullname: Scalars['String']['input']; @@ -268,32 +252,26 @@ export type Query = { users: Array; }; - export type Query_EntitiesArgs = { representations: Array; }; - export type QueryGetProjectArgs = { id: Scalars['String']['input']; }; - export type QueryGetUserArgs = { id: Scalars['ID']['input']; }; - export type QueryInviteArgs = { id: Scalars['ID']['input']; }; - export type QueryInvitesArgs = { status?: InputMaybe; }; - export type QueryProjectUsersArgs = { projectId: Scalars['String']['input']; }; diff --git a/packages/server/src/auth/graphql/sdk.ts b/packages/server/src/auth/graphql/sdk.ts index 21e86e56..2d9e8246 100644 --- a/packages/server/src/auth/graphql/sdk.ts +++ b/packages/server/src/auth/graphql/sdk.ts @@ -12,16 +12,16 @@ export type MakeEmpty = export type Incremental = T | { [P in keyof T]?: P extends ' $fragmentName' | '__typename' ? T[P] : never }; /** All built-in and custom scalars, mapped to their actual values */ export type Scalars = { - ID: { input: string; output: string; } - String: { input: string; output: string; } - Boolean: { input: boolean; output: boolean; } - Int: { input: number; output: number; } - Float: { input: number; output: number; } - DateTime: { input: any; output: any; } - JSON: { input: any; output: any; } - _Any: { input: any; output: any; } - federation__FieldSet: { input: any; output: any; } - link__Import: { input: any; output: any; } + ID: { input: string; output: string }; + String: { input: string; output: string }; + Boolean: { input: boolean; output: boolean }; + Int: { input: number; output: number }; + Float: { input: number; output: number }; + DateTime: { input: any; output: any }; + JSON: { input: any; output: any }; + _Any: { input: any; output: any }; + federation__FieldSet: { input: any; output: any }; + link__Import: { input: any; output: any }; }; /** Input type for accepting an invite */ @@ -119,86 +119,70 @@ export type Mutation = { updateUser: UserModel; }; - export type MutationAcceptInviteArgs = { input: AcceptInviteModel; }; - export type MutationCancelInviteArgs = { id: Scalars['ID']['input']; }; - export type MutationCreateInviteArgs = { email: Scalars['String']['input']; role?: InputMaybe; }; - export type MutationCreateProjectArgs = { project: ProjectCreateInput; }; - export type MutationForgotPasswordArgs = { user: ForgotDto; }; - export type MutationLoginEmailArgs = { user: EmailLoginDto; }; - export type MutationLoginGoogleArgs = { user: GoogleLoginDto; }; - export type MutationLoginUsernameArgs = { user: UsernameLoginDto; }; - export type MutationRefreshArgs = { refreshToken: Scalars['String']['input']; }; - export type MutationResendInviteArgs = { id: Scalars['ID']['input']; }; - export type MutationResetPasswordArgs = { user: ResetDto; }; - export type MutationSignupArgs = { user: UserSignupDto; }; - export type MutationUpdateProjectArgs = { id: Scalars['String']['input']; settings: ConfigurableProjectSettings; }; - export type MutationUpdateProjectAuthMethodsArgs = { id: Scalars['String']['input']; projectAuthMethods: ProjectAuthMethodsInput; }; - export type MutationUpdateProjectSettingsArgs = { id: Scalars['String']['input']; projectSettings: ProjectSettingsInput; }; - export type MutationUpdateUserArgs = { email: Scalars['String']['input']; fullname: Scalars['String']['input']; @@ -271,32 +255,26 @@ export type Query = { users: Array; }; - export type Query_EntitiesArgs = { representations: Array; }; - export type QueryGetProjectArgs = { id: Scalars['String']['input']; }; - export type QueryGetUserArgs = { id: Scalars['ID']['input']; }; - export type QueryInviteArgs = { id: Scalars['ID']['input']; }; - export type QueryInvitesArgs = { status?: InputMaybe; }; - export type QueryProjectUsersArgs = { projectId: Scalars['String']['input']; }; @@ -353,36 +331,64 @@ export type ProjectUsersQueryVariables = Exact<{ projectId: Scalars['String']['input']; }>; - -export type ProjectUsersQuery = { __typename?: 'Query', projectUsers: Array<{ __typename?: 'UserModel', id: string, projectId: string, username?: string | null, fullname?: string | null, email?: string | null, role: number, createdAt: any, updatedAt: any, deletedAt?: any | null }> }; - +export type ProjectUsersQuery = { + __typename?: 'Query'; + projectUsers: Array<{ + __typename?: 'UserModel'; + id: string; + projectId: string; + username?: string | null; + fullname?: string | null; + email?: string | null; + role: number; + createdAt: any; + updatedAt: any; + deletedAt?: any | null; + }>; +}; export const ProjectUsersDocument = gql` - query projectUsers($projectId: String!) { - projectUsers(projectId: $projectId) { - id - projectId - username - fullname - email - role - createdAt - updatedAt - deletedAt + query projectUsers($projectId: String!) { + projectUsers(projectId: $projectId) { + id + projectId + username + fullname + email + role + createdAt + updatedAt + deletedAt + } } -} - `; - -export type SdkFunctionWrapper = (action: (requestHeaders?:Record) => Promise, operationName: string, operationType?: string, variables?: any) => Promise; +`; +export type SdkFunctionWrapper = ( + action: (requestHeaders?: Record) => Promise, + operationName: string, + operationType?: string, + variables?: any +) => Promise; const defaultWrapper: SdkFunctionWrapper = (action, _operationName, _operationType, variables) => action(); export function getSdk(client: GraphQLClient, withWrapper: SdkFunctionWrapper = defaultWrapper) { return { - projectUsers(variables: ProjectUsersQueryVariables, requestHeaders?: GraphQLClientRequestHeaders): Promise { - return withWrapper((wrappedRequestHeaders) => client.request(ProjectUsersDocument, variables, {...requestHeaders, ...wrappedRequestHeaders}), 'projectUsers', 'query', variables); + projectUsers( + variables: ProjectUsersQueryVariables, + requestHeaders?: GraphQLClientRequestHeaders + ): Promise { + return withWrapper( + (wrappedRequestHeaders) => + client.request(ProjectUsersDocument, variables, { + ...requestHeaders, + ...wrappedRequestHeaders + }), + 'projectUsers', + 'query', + variables + ); } }; } -export type Sdk = ReturnType; \ No newline at end of file +export type Sdk = ReturnType; diff --git a/packages/server/src/auth/graphql/users/users.ts b/packages/server/src/auth/graphql/users/users.ts index f24eeaa9..4dc1575c 100644 --- a/packages/server/src/auth/graphql/users/users.ts +++ b/packages/server/src/auth/graphql/users/users.ts @@ -4,28 +4,44 @@ import * as Types from '../graphql'; import { DocumentNode } from 'graphql'; import gql from 'graphql-tag'; -export type UsersQueryVariables = Types.Exact<{ [key: string]: never; }>; - - -export type UsersQuery = { __typename?: 'Query', users: Array<{ __typename?: 'UserModel', id: string, projectId: string, username?: string | null, fullname?: string | null, email?: string | null, role: number, createdAt: any, updatedAt: any, deletedAt?: any | null }> }; +export type UsersQueryVariables = Types.Exact<{ [key: string]: never }>; +export type UsersQuery = { + __typename?: 'Query'; + users: Array<{ + __typename?: 'UserModel'; + id: string; + projectId: string; + username?: string | null; + fullname?: string | null; + email?: string | null; + role: number; + createdAt: any; + updatedAt: any; + deletedAt?: any | null; + }>; +}; export const UsersDocument = gql` - query users { - users { - id - projectId - username - fullname - email - role - createdAt - updatedAt - deletedAt + query users { + users { + id + projectId + username + fullname + email + role + createdAt + updatedAt + deletedAt + } } -} - `; -export type Requester = (doc: DocumentNode, vars?: V, options?: C) => Promise | AsyncIterable +`; +export type Requester = ( + doc: DocumentNode, + vars?: V, + options?: C +) => Promise | AsyncIterable; export function getSdk(requester: Requester) { return { users(variables?: UsersQueryVariables, options?: C): Promise { @@ -33,4 +49,4 @@ export function getSdk(requester: Requester) { } }; } -export type Sdk = ReturnType; \ No newline at end of file +export type Sdk = ReturnType; diff --git a/packages/server/src/permission/permission.service.ts b/packages/server/src/permission/permission.service.ts index 48d185bc..3e49d3d8 100644 --- a/packages/server/src/permission/permission.service.ts +++ b/packages/server/src/permission/permission.service.ts @@ -9,8 +9,10 @@ import { Permission } from './permission.model'; @Injectable() export class PermissionService { - constructor(@Inject(CASBIN_PROVIDER) private readonly enforcer: casbin.Enforcer, - private readonly userService: UserService) {} + constructor( + @Inject(CASBIN_PROVIDER) private readonly enforcer: casbin.Enforcer, + private readonly userService: UserService + ) {} /** requestingUser must be an owner themselves */ async grantOwner(targetUser: string, requestingUser: string, organization: string): Promise { @@ -28,17 +30,19 @@ export class PermissionService { const users = await this.userService.getUsersForProject(requestingUser.projectId); // Create the cooresponding permission representation - const permissions = await Promise.all(users.map(async user => { - const hasRole = await this.enforcer.enforce(user.id, Roles.PROJECT_ADMIN, project._id); - const editable = !(await this.enforcer.enforce(user.id, Roles.OWNER, project._id)); + const permissions = await Promise.all( + users.map(async (user) => { + const hasRole = await this.enforcer.enforce(user.id, Roles.PROJECT_ADMIN, project._id); + const editable = !(await this.enforcer.enforce(user.id, Roles.OWNER, project._id)); - return { - user: user.id, - role: Roles.PROJECT_ADMIN, - hasRole, - editable - }; - })); + return { + user: user.id, + role: Roles.PROJECT_ADMIN, + hasRole, + editable + }; + }) + ); return permissions; } diff --git a/packages/server/src/permission/permissions/project.ts b/packages/server/src/permission/permissions/project.ts index bc4bb5f4..0d055daf 100644 --- a/packages/server/src/permission/permissions/project.ts +++ b/packages/server/src/permission/permissions/project.ts @@ -6,7 +6,7 @@ export enum ProjectPermissions { READ = 'project:read', UPDATE = 'project:update', DELETE = 'project:delete', - GRANT_ADMIN = 'project:grant_admin', + GRANT_ADMIN = 'project:grant_admin' } /** All role to project permissions */