-
Notifications
You must be signed in to change notification settings - Fork 12
fix: refactor institution handling in user authorization and mutation #1289
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
0ca001a
cb446e0
4321cfc
7a75b71
79d7b45
f018c44
2e24f7d
8ad5e9c
ca25c91
bb5ab7d
73656b4
bb1f2fe
236af45
1cefa8a
c70cab6
2f151dc
2ae1bbf
31e296e
b5d2336
6049bf2
acd4ad9
44183f0
b3d170a
20d1d34
4dea7f0
5fd0d68
907caa0
f6fe74a
2f23c67
c031d01
6152327
39f5c64
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -6,22 +6,18 @@ import { OpenIdClient } from '@user-office-software/openid'; | |
| import { ValidTokenSet } from '@user-office-software/openid/lib/model/ValidTokenSet'; | ||
| import { ValidUserInfo } from '@user-office-software/openid/lib/model/ValidUserInfo'; | ||
| import { GraphQLError } from 'graphql'; | ||
| import { UserinfoResponse } from 'openid-client'; | ||
| import { container } from 'tsyringe'; | ||
|
|
||
| import { Tokens } from '../config/Tokens'; | ||
| import { AdminDataSource } from '../datasources/AdminDataSource'; | ||
| import { Institution } from '../models/Institution'; | ||
| import { Rejection } from '../models/Rejection'; | ||
| import { SettingsId } from '../models/Settings'; | ||
| import { AuthJwtPayload, User, UserRole } from '../models/User'; | ||
| import { GetOrCreateInstitutionInput } from '../resolvers/mutations/UpsertUserMutation'; | ||
| import { getInstitutionFromRor } from '../services/RorApi'; | ||
| import { UserAuthorization } from './UserAuthorization'; | ||
|
|
||
| interface UserinfoResponseWithInstitution extends UserinfoResponse { | ||
| institution_ror_id?: string; | ||
| institution_name?: string; | ||
| institution_country?: string; | ||
| } | ||
|
|
||
| export class OAuthAuthorization extends UserAuthorization { | ||
| private db = container.resolve<AdminDataSource>(Tokens.AdminDataSource); | ||
|
|
||
|
|
@@ -85,38 +81,82 @@ export class OAuthAuthorization extends UserAuthorization { | |
| }); | ||
| } | ||
|
|
||
| public async getOrCreateUserInstitution( | ||
| userInfo: UserinfoResponseWithInstitution | ||
| ) { | ||
| if (!userInfo.institution_name || !userInfo.institution_country) { | ||
| return null; | ||
| private async getOrCreateInstitutionByRorId( | ||
| rorId: string | ||
| ): Promise<Institution | null> { | ||
| let institution = await this.adminDataSource.getInstitutionByRorId(rorId); | ||
| if (!institution) { | ||
| const rorData = await getInstitutionFromRor(rorId); | ||
| let name = 'Unknown institution'; | ||
| let countryId: number | null = null; | ||
|
|
||
| if (rorData) { | ||
| name = rorData.name; | ||
| let country = await this.adminDataSource.getCountryByName( | ||
| rorData.country | ||
| ); | ||
| if (!country) { | ||
| logger.logWarn('Country not found in database', { | ||
| countryName: rorData.country, | ||
| }); | ||
| country = await this.adminDataSource.createCountry(rorData.country); | ||
| } | ||
| countryId = country.countryId; | ||
| } | ||
|
|
||
| institution = await this.adminDataSource.createInstitution({ | ||
| name: name, | ||
| country: countryId, | ||
| rorId: rorId, | ||
| }); | ||
| } | ||
|
|
||
| let institution = userInfo.institution_ror_id | ||
| ? await this.adminDataSource.getInstitutionByRorId( | ||
| userInfo.institution_ror_id | ||
| ) | ||
| : await this.adminDataSource.getInstitutionByName( | ||
| userInfo.institution_name | ||
| ); | ||
| return institution; | ||
| } | ||
|
jekabs-karklins marked this conversation as resolved.
|
||
|
|
||
| private async getOrCreateInstitutionByManualInput(manualInput: { | ||
| name: string; | ||
| country: string; | ||
| }): Promise<Institution | null> { | ||
| const institutionName = manualInput.name; | ||
| const countryName = manualInput.country; | ||
|
|
||
| let country = await this.adminDataSource.getCountryByName(countryName); | ||
| if (!country) { | ||
| // create country if it does not exist | ||
| country = await this.adminDataSource.createCountry(countryName); | ||
| } | ||
|
|
||
| let institution = | ||
| await this.adminDataSource.getInstitutionByName(institutionName); | ||
|
|
||
| // If the institution exists but the country does not match, we should create a new institution | ||
| if (institution && institution.country !== country.countryId) { | ||
| institution = null; | ||
| } | ||
|
|
||
| if (!institution) { | ||
| let institutionCountry = await this.adminDataSource.getCountryByName( | ||
| userInfo.institution_country | ||
| ); | ||
| institution = await this.adminDataSource.createInstitution({ | ||
| name: institutionName, | ||
| country: country.countryId, | ||
| rorId: undefined, | ||
| }); | ||
| } | ||
|
|
||
| if (!institutionCountry) { | ||
| institutionCountry = await this.adminDataSource.createCountry( | ||
| userInfo.institution_country | ||
| ); | ||
| return institution; | ||
| } | ||
|
|
||
| public async getOrCreateUserInstitution(input: GetOrCreateInstitutionInput) { | ||
| let institution: Institution | null = null; | ||
| if (typeof input === 'string') { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Will it add more value to validate rorID with more specific rule. Since the rorID is very standard, we can enforce the validation based on its pattern. https://ror.readme.io/docs/identifier Just an add-on
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good idea, I have added the validation schema to make sure all user input is valid at the get-go |
||
| // ROR ID provided | ||
| if (!input || input.trim() === '') { | ||
| return null; | ||
| } | ||
| const newInstitution = { | ||
| name: userInfo.institution_name, | ||
| country: institutionCountry.countryId, | ||
| rorId: userInfo.institution_ror_id, | ||
| }; | ||
| institution = | ||
| await this.adminDataSource.createInstitution(newInstitution); | ||
| institution = await this.getOrCreateInstitutionByRorId(input); | ||
| } else if (input !== null && typeof input === 'object') { | ||
| // Manual institution details provided | ||
| institution = await this.getOrCreateInstitutionByManualInput(input); | ||
| } | ||
|
|
||
| return institution; | ||
|
|
@@ -127,7 +167,17 @@ export class OAuthAuthorization extends UserAuthorization { | |
| tokenSet: ValidTokenSet | ||
| ): Promise<User> { | ||
| const client = await OpenIdClient.getInstance(); | ||
| const institution = await this.getOrCreateUserInstitution(userInfo); | ||
| let institutionInput: GetOrCreateInstitutionInput = null; | ||
| if (userInfo.institution_ror_id) { | ||
| institutionInput = userInfo.institution_ror_id as string; | ||
| } else if (userInfo.institution_name && userInfo.institution_country) { | ||
| institutionInput = { | ||
| country: userInfo.institution_country as string, | ||
| name: userInfo.institution_name as string, | ||
| }; | ||
| } | ||
|
|
||
| const institution = await this.getOrCreateUserInstitution(institutionInput); | ||
| const userId = this.getUniqueId(userInfo); | ||
| const userWithOAuthSubMatch = | ||
| await this.userDataSource.getByOIDCSub(userId); | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.