From f04c369e89e4e00d8c23e17fffc0a4fa88ca03d8 Mon Sep 17 00:00:00 2001 From: Matthew <50069872+SavelevMatthew@users.noreply.github.com> Date: Wed, 21 Feb 2024 23:59:53 +0500 Subject: [PATCH] feat(condo): INFRA-264 OIDC Enabled field and deletedAt fix (#4344) * feat(condo): INFRA-264 OIDC Enabled field and deletedAt fix * feat(condo): INFRA-264 regenerate migration and throw separate error * feat(condo): INFRA-264 changed error message --- apps/condo/bin/create-oidc-client.js | 1 + .../oidc/adapter/OidcModelClientAdapter.js | 6 +- apps/condo/domains/user/schema/OidcClient.js | 11 + apps/condo/domains/user/schema/_oidc.test.js | 819 ++++++++++-------- .../domains/user/utils/testSchema/index.js | 1 + ...0519-0368_oidcclient_isenabled_and_more.js | 35 + apps/condo/schema.graphql | 17 + apps/condo/schema.ts | 15 + 8 files changed, 542 insertions(+), 363 deletions(-) create mode 100644 apps/condo/migrations/20240221230519-0368_oidcclient_isenabled_and_more.js diff --git a/apps/condo/bin/create-oidc-client.js b/apps/condo/bin/create-oidc-client.js index df09fa060bf..aa5f4bda883 100644 --- a/apps/condo/bin/create-oidc-client.js +++ b/apps/condo/bin/create-oidc-client.js @@ -15,6 +15,7 @@ async function main (args) { client_id: clientId, client_secret: clientSecret, redirect_uris: [redirectUri], // using uri as redirect_uri to show the ID Token contents + isEnabled: true, response_types: ['code id_token', 'code', 'id_token'], grant_types: ['implicit', 'authorization_code', 'refresh_token'], // 'implicit', 'authorization_code', 'refresh_token', or 'urn:ietf:params:oauth:grant-type:device_code' token_endpoint_auth_method: 'client_secret_basic', diff --git a/apps/condo/domains/user/oidc/adapter/OidcModelClientAdapter.js b/apps/condo/domains/user/oidc/adapter/OidcModelClientAdapter.js index a8ea278c468..2918f2e55b1 100644 --- a/apps/condo/domains/user/oidc/adapter/OidcModelClientAdapter.js +++ b/apps/condo/domains/user/oidc/adapter/OidcModelClientAdapter.js @@ -165,7 +165,7 @@ class OidcModelClientAdapter { fingerprint: OIDC_FINGERPRINT, }, } - const item = await OidcClient.getOne(this.context, { clientId: id }) + const item = await OidcClient.getOne(this.context, { clientId: id, deletedAt: null }) if (!item) { return await OidcClient.create(this.context, { ...dvAndSender, clientId: id, payload, expiresAt }) } else { @@ -184,9 +184,9 @@ class OidcModelClientAdapter { * */ async find (id) { - const item = await OidcClient.getOne(this.context, { clientId: id }) + const item = await OidcClient.getOne(this.context, { clientId: id, isEnabled: true, deletedAt: null }) if (!item) { - throw new Error(`There is no OIDC client with clientId=${id}`) + throw new Error(`There is no active OIDC client with clientId=${id}`) } return item.payload } diff --git a/apps/condo/domains/user/schema/OidcClient.js b/apps/condo/domains/user/schema/OidcClient.js index c5cda2b636a..16325426f85 100644 --- a/apps/condo/domains/user/schema/OidcClient.js +++ b/apps/condo/domains/user/schema/OidcClient.js @@ -65,6 +65,17 @@ const OidcClient = new GQLListSchema('OidcClient', { type: 'Text', }, + isEnabled: { + schemaDoc: + 'A switch that allows you to disable some OIDC clients. ' + + 'If an OIDC client is disabled, it cannot be used for OIDC authorization. ' + + 'Used mainly by developers portal to create OIDC client before publishing an application, ' + + 'and enable OIDC after publishing.', + type: 'Checkbox', + isRequired: true, + defaultValue: false, + }, + meta: { schemaDoc: 'The additional client data', type: 'Json', diff --git a/apps/condo/domains/user/schema/_oidc.test.js b/apps/condo/domains/user/schema/_oidc.test.js index 7a1ec8c2f54..9dd28bb4a72 100644 --- a/apps/condo/domains/user/schema/_oidc.test.js +++ b/apps/condo/domains/user/schema/_oidc.test.js @@ -1,16 +1,18 @@ const { faker } = require('@faker-js/faker') const { default: axios } = require('axios') +const dayjs = require('dayjs') const jwtDecode = require('jwt-decode') const fetch = require('node-fetch') const { Issuer, generators } = require('openid-client') const { - createAxiosClientWithCookie, getRandomString, makeLoggedInAdminClient, + createAxiosClientWithCookie, getRandomString, makeLoggedInAdminClient, catchErrorFrom, } = require('@open-condo/keystone/test.utils') const { makeClientWithNewRegisteredAndLoggedInUser, createTestOidcClient, + updateTestOidcClient, } = require('@condo/domains/user/utils/testSchema') const { createAdapterClass } = require('../oidc/adapter') @@ -113,411 +115,508 @@ test('getCookie test util', async () => { }) describe('OIDC', () => { - test('case: code + id_token with refresh (code flow)', async () => { - const uri = 'https://jwt.io/' - const clientId = getRandomString() - const clientSecret = getRandomString() - const admin = await makeLoggedInAdminClient() - - await createTestOidcClient(admin, { - payload: { - // application_type, client_id, client_name, client_secret, client_uri, contacts, default_acr_values, default_max_age, grant_types, id_token_signed_response_alg, initiate_login_uri, jwks, jwks_uri, logo_uri, policy_uri, post_logout_redirect_uris, redirect_uris, require_auth_time, response_types, scope, sector_identifier_uri, subject_type, token_endpoint_auth_method, tos_uri, userinfo_signed_response_alg + describe('OIDCClient logic tests', () => { + test('Soft-deleted OIDCClient must not be used for authorization', async () => { + const uri = 'https://jwt.io/' + const clientId = getRandomString() + const clientSecret = getRandomString() + const admin = await makeLoggedInAdminClient() + + const [oidcClient] = await createTestOidcClient(admin, { + payload: { + // application_type, client_id, client_name, client_secret, client_uri, contacts, default_acr_values, default_max_age, grant_types, id_token_signed_response_alg, initiate_login_uri, jwks, jwks_uri, logo_uri, policy_uri, post_logout_redirect_uris, redirect_uris, require_auth_time, response_types, scope, sector_identifier_uri, subject_type, token_endpoint_auth_method, tos_uri, userinfo_signed_response_alg + client_id: clientId, + client_secret: clientSecret, + redirect_uris: [uri], // using uri as redirect_uri to show the ID Token contents + response_types: ['code id_token', 'code', 'id_token'], + grant_types: ['implicit', 'authorization_code', 'refresh_token'], // 'implicit', 'authorization_code', 'refresh_token', or 'urn:ietf:params:oauth:grant-type:device_code' + token_endpoint_auth_method: 'client_secret_basic', + }, + }) + const [deletedClient] = await updateTestOidcClient(admin, oidcClient.id, { deletedAt: dayjs().toISOString() }) + expect(deletedClient).toHaveProperty('deletedAt') + expect(deletedClient.deletedAt).not.toBeNull() + + const c = await makeClientWithNewRegisteredAndLoggedInUser() + expectCookieKeys(c.getCookie(), ['keystone.sid']) + + // 1) server side ( create oidc client and prepare oidcAuthUrl ) + + const oidcIssuer = await Issuer.discover(`${c.serverUrl}/oidc`) + const serverSideOidcClient = new oidcIssuer.Client({ client_id: clientId, client_secret: clientSecret, redirect_uris: [uri], // using uri as redirect_uri to show the ID Token contents - response_types: ['code id_token', 'code', 'id_token'], - grant_types: ['implicit', 'authorization_code', 'refresh_token'], // 'implicit', 'authorization_code', 'refresh_token', or 'urn:ietf:params:oauth:grant-type:device_code' + response_types: ['code id_token'], token_endpoint_auth_method: 'client_secret_basic', - }, - }) - - const c = await makeClientWithNewRegisteredAndLoggedInUser() - expectCookieKeys(c.getCookie(), ['keystone.sid']) - - // 1) server side ( create oidc client and prepare oidcAuthUrl ) - - const oidcIssuer = await Issuer.discover(`${c.serverUrl}/oidc`) - const serverSideOidcClient = new oidcIssuer.Client({ - client_id: clientId, - client_secret: clientSecret, - redirect_uris: [uri], // using uri as redirect_uri to show the ID Token contents - response_types: ['code id_token'], - token_endpoint_auth_method: 'client_secret_basic', - }) - - const nonce = generators.nonce() - const url = new URL(serverSideOidcClient.authorizationUrl({ nonce })) - const oidcAuthUrl = `${c.serverUrl}${url.pathname}${url.search}` - - // 2) client side ( open oidcAuthUrl ) - - const res1 = await request(oidcAuthUrl, c.getCookie()) - expect(res1.status).toBe(303) - expect(res1.headers.location.startsWith('/oidc/interaction/')).toBeTruthy() - expectCookieKeys(res1.cookie, ['keystone.sid', '_interaction', '_interaction.sig', '_interaction_resume', '_interaction_resume.sig']) - - const res2 = await request(`${c.serverUrl}${res1.headers.location}`, res1.cookie) - expect(res2.status).toBe(303) - expect(res2.headers.location.startsWith(`${c.serverUrl}/oidc/auth/`)).toBeTruthy() - expectCookieKeys(res2.cookie, ['keystone.sid', '_interaction', '_interaction.sig', '_interaction_resume', '_interaction_resume.sig']) - - const res3 = await request(res2.headers.location, res2.cookie) - expect(res3.status).toBe(303) - expect(res3.headers.location.startsWith(`${uri}#code`)).toBeTruthy() - expectCookieKeys(res3.cookie, ['keystone.sid', '_interaction', '_interaction.sig', '_interaction_resume', '_interaction_resume.sig', '_session', '_session.sig']) - - // 3) callback to server side ( callback with code to oidc app site; server get the access and cann use it ) - - const params = serverSideOidcClient.callbackParams(res3.headers.location.replace(`${uri}#`, `${uri}?`)) - const tokenSet = await serverSideOidcClient.callback(uri, { code: params.code }, { nonce }) - expect(tokenSet.access_token).toHaveLength(43) // important to be 43! - expect(tokenSet.id_token.length > 10).toBeTruthy() - - // 4) check requests by accessToken to /oidc/userinfo - - const userinfo = await serverSideOidcClient.userinfo(tokenSet.access_token) - expect(userinfo).toEqual({ - 'sub': c.user.id, - 'type': 'staff', - 'v': 1, - 'dv': 1, - 'isAdmin': false, - 'isSupport': false, - 'name': c.user.name, - }) - expect(await getAccessToken(tokenSet.access_token, c)).toMatchObject({ - 'accountId': c.user.id, - 'clientId': clientId, - 'expiresWithSession': true, - 'gty': 'authorization_code', - 'kind': 'AccessToken', - 'scope': 'openid', + }) + + const nonce = generators.nonce() + const url = new URL(serverSideOidcClient.authorizationUrl({ nonce })) + const oidcAuthUrl = `${c.serverUrl}${url.pathname}${url.search}` + + // 2) client side ( open oidcAuthUrl ) + await catchErrorFrom(async () => { + await request(oidcAuthUrl, c.getCookie()) + }, (error) => { + expect(error).toHaveProperty('message', 'Request failed with status code 500') + expect(error).toHaveProperty(['request', 'res', 'statusCode'], 500) + }) }) - - // 5) check requests by accessToken to /admin/api - - await expectToGetAuthenticatedUser(`${c.serverUrl}/admin/api`, null) - await expectToGetAuthenticatedUser( - `${c.serverUrl}/admin/api`, - { id: c.user.id, name: c.user.name }, - { 'Authorization': `Bearer ${tokenSet.access_token}` }, - ) - - // 6) check code 401 when token expired - - const someWrongToken = faker.random.alpha({ - count: 43, + test('OIDCClient with "isEnabled: false" must not be used for authorization', async () => { + const uri = 'https://jwt.io/' + const clientId = getRandomString() + const clientSecret = getRandomString() + const admin = await makeLoggedInAdminClient() + + await createTestOidcClient(admin, { + isEnabled: false, + payload: { + // application_type, client_id, client_name, client_secret, client_uri, contacts, default_acr_values, default_max_age, grant_types, id_token_signed_response_alg, initiate_login_uri, jwks, jwks_uri, logo_uri, policy_uri, post_logout_redirect_uris, redirect_uris, require_auth_time, response_types, scope, sector_identifier_uri, subject_type, token_endpoint_auth_method, tos_uri, userinfo_signed_response_alg + client_id: clientId, + client_secret: clientSecret, + redirect_uris: [uri], // using uri as redirect_uri to show the ID Token contents + response_types: ['code id_token', 'code', 'id_token'], + grant_types: ['implicit', 'authorization_code', 'refresh_token'], // 'implicit', 'authorization_code', 'refresh_token', or 'urn:ietf:params:oauth:grant-type:device_code' + token_endpoint_auth_method: 'client_secret_basic', + }, + }) + + const c = await makeClientWithNewRegisteredAndLoggedInUser() + expectCookieKeys(c.getCookie(), ['keystone.sid']) + + // 1) server side ( create oidc client and prepare oidcAuthUrl ) + + const oidcIssuer = await Issuer.discover(`${c.serverUrl}/oidc`) + const serverSideOidcClient = new oidcIssuer.Client({ + client_id: clientId, + client_secret: clientSecret, + redirect_uris: [uri], // using uri as redirect_uri to show the ID Token contents + response_types: ['code id_token'], + token_endpoint_auth_method: 'client_secret_basic', + }) + + const nonce = generators.nonce() + const url = new URL(serverSideOidcClient.authorizationUrl({ nonce })) + const oidcAuthUrl = `${c.serverUrl}${url.pathname}${url.search}` + + // 2) client side ( open oidcAuthUrl ) + await catchErrorFrom(async () => { + await request(oidcAuthUrl, c.getCookie()) + }, (error) => { + expect(error).toHaveProperty('message', 'Request failed with status code 500') + expect(error).toHaveProperty(['request', 'res', 'statusCode'], 500) + }) }) - await expectToGetUnauthenticatedUser( - `${c.serverUrl}/admin/api`, - { - authorization: `Bearer ${someWrongToken}`, - }, - ) - - // 7) using of refresh token to get new access token - - const newTokenSet = await serverSideOidcClient.refresh(tokenSet) - expect(newTokenSet.access_token).not.toBe(tokenSet.access_token) - expect(newTokenSet.expires_at).toBeGreaterThanOrEqual(tokenSet.expires_at) }) + describe('Real-life cases', () => { - test('case: code without refresh (code flow)', async () => { - const uri = 'https://jwt.io/' - const clientId = getRandomString() - const clientSecret = getRandomString() - const admin = await makeLoggedInAdminClient() + test('case: code + id_token with refresh (code flow)', async () => { + const uri = 'https://jwt.io/' + const clientId = getRandomString() + const clientSecret = getRandomString() + const admin = await makeLoggedInAdminClient() - await createTestOidcClient(admin, { - payload: { + await createTestOidcClient(admin, { + payload: { + // application_type, client_id, client_name, client_secret, client_uri, contacts, default_acr_values, default_max_age, grant_types, id_token_signed_response_alg, initiate_login_uri, jwks, jwks_uri, logo_uri, policy_uri, post_logout_redirect_uris, redirect_uris, require_auth_time, response_types, scope, sector_identifier_uri, subject_type, token_endpoint_auth_method, tos_uri, userinfo_signed_response_alg + client_id: clientId, + client_secret: clientSecret, + redirect_uris: [uri], // using uri as redirect_uri to show the ID Token contents + response_types: ['code id_token', 'code', 'id_token'], + grant_types: ['implicit', 'authorization_code', 'refresh_token'], // 'implicit', 'authorization_code', 'refresh_token', or 'urn:ietf:params:oauth:grant-type:device_code' + token_endpoint_auth_method: 'client_secret_basic', + }, + }) + + const c = await makeClientWithNewRegisteredAndLoggedInUser() + expectCookieKeys(c.getCookie(), ['keystone.sid']) + + // 1) server side ( create oidc client and prepare oidcAuthUrl ) + + const oidcIssuer = await Issuer.discover(`${c.serverUrl}/oidc`) + const serverSideOidcClient = new oidcIssuer.Client({ client_id: clientId, client_secret: clientSecret, - redirect_uris: [uri], - response_types: ['code'], - grant_types: ['authorization_code'], + redirect_uris: [uri], // using uri as redirect_uri to show the ID Token contents + response_types: ['code id_token'], token_endpoint_auth_method: 'client_secret_basic', - }, - }) - - const c = await makeClientWithNewRegisteredAndLoggedInUser() - expectCookieKeys(c.getCookie(), ['keystone.sid']) - - // 1) server side ( create oidc client and prepare oidcAuthUrl ) - - const oidcIssuer = await Issuer.discover(`${c.serverUrl}/oidc`) - const serverSideOidcClient = new oidcIssuer.Client({ - client_id: clientId, - client_secret: clientSecret, - redirect_uris: [uri], // using uri as redirect_uri to show the ID Token contents - response_types: ['code'], - token_endpoint_auth_method: 'client_secret_basic', + }) + + const nonce = generators.nonce() + const url = new URL(serverSideOidcClient.authorizationUrl({ nonce })) + const oidcAuthUrl = `${c.serverUrl}${url.pathname}${url.search}` + + // 2) client side ( open oidcAuthUrl ) + + const res1 = await request(oidcAuthUrl, c.getCookie()) + expect(res1.status).toBe(303) + expect(res1.headers.location.startsWith('/oidc/interaction/')).toBeTruthy() + expectCookieKeys(res1.cookie, ['keystone.sid', '_interaction', '_interaction.sig', '_interaction_resume', '_interaction_resume.sig']) + + const res2 = await request(`${c.serverUrl}${res1.headers.location}`, res1.cookie) + expect(res2.status).toBe(303) + expect(res2.headers.location.startsWith(`${c.serverUrl}/oidc/auth/`)).toBeTruthy() + expectCookieKeys(res2.cookie, ['keystone.sid', '_interaction', '_interaction.sig', '_interaction_resume', '_interaction_resume.sig']) + + const res3 = await request(res2.headers.location, res2.cookie) + expect(res3.status).toBe(303) + expect(res3.headers.location.startsWith(`${uri}#code`)).toBeTruthy() + expectCookieKeys(res3.cookie, ['keystone.sid', '_interaction', '_interaction.sig', '_interaction_resume', '_interaction_resume.sig', '_session', '_session.sig']) + + // 3) callback to server side ( callback with code to oidc app site; server get the access and cann use it ) + + const params = serverSideOidcClient.callbackParams(res3.headers.location.replace(`${uri}#`, `${uri}?`)) + const tokenSet = await serverSideOidcClient.callback(uri, { code: params.code }, { nonce }) + expect(tokenSet.access_token).toHaveLength(43) // important to be 43! + expect(tokenSet.id_token.length > 10).toBeTruthy() + + // 4) check requests by accessToken to /oidc/userinfo + + const userinfo = await serverSideOidcClient.userinfo(tokenSet.access_token) + expect(userinfo).toEqual({ + 'sub': c.user.id, + 'type': 'staff', + 'v': 1, + 'dv': 1, + 'isAdmin': false, + 'isSupport': false, + 'name': c.user.name, + }) + expect(await getAccessToken(tokenSet.access_token, c)).toMatchObject({ + 'accountId': c.user.id, + 'clientId': clientId, + 'expiresWithSession': true, + 'gty': 'authorization_code', + 'kind': 'AccessToken', + 'scope': 'openid', + }) + + // 5) check requests by accessToken to /admin/api + + await expectToGetAuthenticatedUser(`${c.serverUrl}/admin/api`, null) + await expectToGetAuthenticatedUser( + `${c.serverUrl}/admin/api`, + { id: c.user.id, name: c.user.name }, + { 'Authorization': `Bearer ${tokenSet.access_token}` }, + ) + + // 6) check code 401 when token expired + + const someWrongToken = faker.random.alpha({ + count: 43, + }) + await expectToGetUnauthenticatedUser( + `${c.serverUrl}/admin/api`, + { + authorization: `Bearer ${someWrongToken}`, + }, + ) + + // 7) using of refresh token to get new access token + + const newTokenSet = await serverSideOidcClient.refresh(tokenSet) + expect(newTokenSet.access_token).not.toBe(tokenSet.access_token) + expect(newTokenSet.expires_at).toBeGreaterThanOrEqual(tokenSet.expires_at) }) - const nonce = generators.nonce() - const url = new URL(serverSideOidcClient.authorizationUrl({ nonce })) - const oidcAuthUrl = `${c.serverUrl}${url.pathname}${url.search}` - - // 2) client side ( open oidcAuthUrl ) - - const res1 = await request(oidcAuthUrl, c.getCookie()) - expect(res1.status).toBe(303) - expect(res1.headers.location.startsWith('/oidc/interaction/')).toBeTruthy() - expectCookieKeys(res1.cookie, ['keystone.sid', '_interaction', '_interaction.sig', '_interaction_resume', '_interaction_resume.sig']) - - const res2 = await request(`${c.serverUrl}${res1.headers.location}`, res1.cookie) - expect(res2.status).toBe(303) - expect(res2.headers.location.startsWith(`${c.serverUrl}/oidc/auth/`)).toBeTruthy() - expectCookieKeys(res2.cookie, ['keystone.sid', '_interaction', '_interaction.sig', '_interaction_resume', '_interaction_resume.sig']) - - const res3 = await request(res2.headers.location, res2.cookie) - expect(res3.status).toBe(303) - expect(res3.headers.location).toContain(`${uri}?code=`) - expectCookieKeys(res3.cookie, ['keystone.sid', '_interaction', '_interaction.sig', '_interaction_resume', '_interaction_resume.sig', '_session', '_session.sig']) - - // 3) callback to server side ( callback with code to oidc app site; server get the access and cann use it ) - - const params = serverSideOidcClient.callbackParams(res3.headers.location) - expect(new Set(Object.keys(params))).toEqual(new Set(['code'])) - const tokenSet = await serverSideOidcClient.callback(uri, { code: params.code }, { nonce }) - expect(tokenSet.access_token).toHaveLength(43) // important to be 43! - expect(tokenSet.id_token.length > 10).toBeTruthy() - - // 4) check requests by accessToken to /oidc/userinfo - - const userinfo = await serverSideOidcClient.userinfo(tokenSet.access_token) - expect(userinfo).toEqual({ - 'sub': c.user.id, - 'type': 'staff', - 'v': 1, - 'dv': 1, - 'isAdmin': false, - 'isSupport': false, - 'name': c.user.name, - }) - expect(await getAccessToken(tokenSet.access_token, c)).toMatchObject({ - 'accountId': c.user.id, - 'clientId': clientId, - 'expiresWithSession': true, - 'gty': 'authorization_code', - 'kind': 'AccessToken', - 'scope': 'openid', + test('case: code without refresh (code flow)', async () => { + const uri = 'https://jwt.io/' + const clientId = getRandomString() + const clientSecret = getRandomString() + const admin = await makeLoggedInAdminClient() + + await createTestOidcClient(admin, { + payload: { + client_id: clientId, + client_secret: clientSecret, + redirect_uris: [uri], + response_types: ['code'], + grant_types: ['authorization_code'], + token_endpoint_auth_method: 'client_secret_basic', + }, + }) + + const c = await makeClientWithNewRegisteredAndLoggedInUser() + expectCookieKeys(c.getCookie(), ['keystone.sid']) + + // 1) server side ( create oidc client and prepare oidcAuthUrl ) + + const oidcIssuer = await Issuer.discover(`${c.serverUrl}/oidc`) + const serverSideOidcClient = new oidcIssuer.Client({ + client_id: clientId, + client_secret: clientSecret, + redirect_uris: [uri], // using uri as redirect_uri to show the ID Token contents + response_types: ['code'], + token_endpoint_auth_method: 'client_secret_basic', + }) + + const nonce = generators.nonce() + const url = new URL(serverSideOidcClient.authorizationUrl({ nonce })) + const oidcAuthUrl = `${c.serverUrl}${url.pathname}${url.search}` + + // 2) client side ( open oidcAuthUrl ) + + const res1 = await request(oidcAuthUrl, c.getCookie()) + expect(res1.status).toBe(303) + expect(res1.headers.location.startsWith('/oidc/interaction/')).toBeTruthy() + expectCookieKeys(res1.cookie, ['keystone.sid', '_interaction', '_interaction.sig', '_interaction_resume', '_interaction_resume.sig']) + + const res2 = await request(`${c.serverUrl}${res1.headers.location}`, res1.cookie) + expect(res2.status).toBe(303) + expect(res2.headers.location.startsWith(`${c.serverUrl}/oidc/auth/`)).toBeTruthy() + expectCookieKeys(res2.cookie, ['keystone.sid', '_interaction', '_interaction.sig', '_interaction_resume', '_interaction_resume.sig']) + + const res3 = await request(res2.headers.location, res2.cookie) + expect(res3.status).toBe(303) + expect(res3.headers.location).toContain(`${uri}?code=`) + expectCookieKeys(res3.cookie, ['keystone.sid', '_interaction', '_interaction.sig', '_interaction_resume', '_interaction_resume.sig', '_session', '_session.sig']) + + // 3) callback to server side ( callback with code to oidc app site; server get the access and cann use it ) + + const params = serverSideOidcClient.callbackParams(res3.headers.location) + expect(new Set(Object.keys(params))).toEqual(new Set(['code'])) + const tokenSet = await serverSideOidcClient.callback(uri, { code: params.code }, { nonce }) + expect(tokenSet.access_token).toHaveLength(43) // important to be 43! + expect(tokenSet.id_token.length > 10).toBeTruthy() + + // 4) check requests by accessToken to /oidc/userinfo + + const userinfo = await serverSideOidcClient.userinfo(tokenSet.access_token) + expect(userinfo).toEqual({ + 'sub': c.user.id, + 'type': 'staff', + 'v': 1, + 'dv': 1, + 'isAdmin': false, + 'isSupport': false, + 'name': c.user.name, + }) + expect(await getAccessToken(tokenSet.access_token, c)).toMatchObject({ + 'accountId': c.user.id, + 'clientId': clientId, + 'expiresWithSession': true, + 'gty': 'authorization_code', + 'kind': 'AccessToken', + 'scope': 'openid', + }) + + // 5) check requests by accessToken to /admin/api + + await expectToGetAuthenticatedUser(`${c.serverUrl}/admin/api`, null) + await expectToGetAuthenticatedUser( + `${c.serverUrl}/admin/api`, + { id: c.user.id, name: c.user.name }, + { 'Authorization': `Bearer ${tokenSet.access_token}` }, + ) + + // 7) no refresh token + await expect(serverSideOidcClient.refresh(tokenSet)).rejects.toThrow('refresh_token not present in TokenSet') }) - // 5) check requests by accessToken to /admin/api - - await expectToGetAuthenticatedUser(`${c.serverUrl}/admin/api`, null) - await expectToGetAuthenticatedUser( - `${c.serverUrl}/admin/api`, - { id: c.user.id, name: c.user.name }, - { 'Authorization': `Bearer ${tokenSet.access_token}` }, - ) - - // 7) no refresh token - await expect(serverSideOidcClient.refresh(tokenSet)).rejects.toThrow('refresh_token not present in TokenSet') - }) - - test('case: token (implicit flow)', async () => { - const uri = 'https://jwt.io/' - const clientId = getRandomString() - const clientSecret = getRandomString() - const admin = await makeLoggedInAdminClient() + test('case: token (implicit flow)', async () => { + const uri = 'https://jwt.io/' + const clientId = getRandomString() + const clientSecret = getRandomString() + const admin = await makeLoggedInAdminClient() - await createTestOidcClient(admin, { - payload: { + await createTestOidcClient(admin, { + payload: { // application_type, client_id, client_name, client_secret, client_uri, contacts, default_acr_values, default_max_age, grant_types, id_token_signed_response_alg, initiate_login_uri, jwks, jwks_uri, logo_uri, policy_uri, post_logout_redirect_uris, redirect_uris, require_auth_time, response_types, scope, sector_identifier_uri, subject_type, token_endpoint_auth_method, tos_uri, userinfo_signed_response_alg + client_id: clientId, + client_secret: clientSecret, + redirect_uris: [uri], // using uri as redirect_uri to show the ID Token contents + response_types: ['id_token token'], + grant_types: ['implicit'], // 'implicit', 'authorization_code', 'refresh_token', or 'urn:ietf:params:oauth:grant-type:device_code' + token_endpoint_auth_method: 'client_secret_basic', + }, + }) + + const c = await makeClientWithNewRegisteredAndLoggedInUser() + expectCookieKeys(c.getCookie(), ['keystone.sid']) + + // 1) server side ( create oidc client and prepare oidcAuthUrl ) + + const oidcIssuer = await Issuer.discover(`${c.serverUrl}/oidc`) + const serverSideOidcClient = new oidcIssuer.Client({ client_id: clientId, client_secret: clientSecret, redirect_uris: [uri], // using uri as redirect_uri to show the ID Token contents response_types: ['id_token token'], - grant_types: ['implicit'], // 'implicit', 'authorization_code', 'refresh_token', or 'urn:ietf:params:oauth:grant-type:device_code' token_endpoint_auth_method: 'client_secret_basic', - }, - }) - - const c = await makeClientWithNewRegisteredAndLoggedInUser() - expectCookieKeys(c.getCookie(), ['keystone.sid']) - - // 1) server side ( create oidc client and prepare oidcAuthUrl ) - - const oidcIssuer = await Issuer.discover(`${c.serverUrl}/oidc`) - const serverSideOidcClient = new oidcIssuer.Client({ - client_id: clientId, - client_secret: clientSecret, - redirect_uris: [uri], // using uri as redirect_uri to show the ID Token contents - response_types: ['id_token token'], - token_endpoint_auth_method: 'client_secret_basic', - }) - - const nonce = generators.nonce() - const url = new URL(serverSideOidcClient.authorizationUrl({ nonce })) - const oidcAuthUrl = `${c.serverUrl}${url.pathname}${url.search}` - - // 2) client side ( open oidcAuthUrl ) - - const res1 = await request(oidcAuthUrl, c.getCookie()) - expect(res1.status).toBe(303) - expect(res1.headers.location.startsWith('/oidc/interaction/')).toBeTruthy() - expectCookieKeys(res1.cookie, ['keystone.sid', '_interaction', '_interaction.sig', '_interaction_resume', '_interaction_resume.sig']) - - const res2 = await request(`${c.serverUrl}${res1.headers.location}`, res1.cookie) - expect(res2.status).toBe(303) - expect(res2.headers.location.startsWith(`${c.serverUrl}/oidc/auth/`)).toBeTruthy() - expectCookieKeys(res2.cookie, ['keystone.sid', '_interaction', '_interaction.sig', '_interaction_resume', '_interaction_resume.sig']) - - const res3 = await request(res2.headers.location, res2.cookie) - expect(res3.status).toBe(303) - console.log(res3.headers.location) - expect(res3.headers.location.startsWith(`${uri}#`)).toBeTruthy() - expectCookieKeys(res3.cookie, ['keystone.sid', '_interaction', '_interaction.sig', '_interaction_resume', '_interaction_resume.sig', '_session', '_session.sig']) - - // 3) callback to server side ( callback with code to oidc app site; server get the access and cann use it ) - - const params = serverSideOidcClient.callbackParams(res3.headers.location.replace(`${uri}#`, `${uri}?`)) - expect(new Set(Object.keys(params))).toEqual(new Set(['access_token', 'id_token', 'expires_in', 'token_type'])) - expect(params.access_token).toHaveLength(43) // important to be 43! - expect(params.token_type).toEqual('Bearer') - expect(params.id_token.length > 10).toBeTruthy() - - // 4) check requests by accessToken to /oidc/userinfo - - const userinfo = await serverSideOidcClient.userinfo(params.access_token) - expect(userinfo).toEqual({ - 'sub': c.user.id, - 'type': 'staff', - 'v': 1, - 'dv': 1, - 'isAdmin': false, - 'isSupport': false, - 'name': c.user.name, + }) + + const nonce = generators.nonce() + const url = new URL(serverSideOidcClient.authorizationUrl({ nonce })) + const oidcAuthUrl = `${c.serverUrl}${url.pathname}${url.search}` + + // 2) client side ( open oidcAuthUrl ) + + const res1 = await request(oidcAuthUrl, c.getCookie()) + expect(res1.status).toBe(303) + expect(res1.headers.location.startsWith('/oidc/interaction/')).toBeTruthy() + expectCookieKeys(res1.cookie, ['keystone.sid', '_interaction', '_interaction.sig', '_interaction_resume', '_interaction_resume.sig']) + + const res2 = await request(`${c.serverUrl}${res1.headers.location}`, res1.cookie) + expect(res2.status).toBe(303) + expect(res2.headers.location.startsWith(`${c.serverUrl}/oidc/auth/`)).toBeTruthy() + expectCookieKeys(res2.cookie, ['keystone.sid', '_interaction', '_interaction.sig', '_interaction_resume', '_interaction_resume.sig']) + + const res3 = await request(res2.headers.location, res2.cookie) + expect(res3.status).toBe(303) + console.log(res3.headers.location) + expect(res3.headers.location.startsWith(`${uri}#`)).toBeTruthy() + expectCookieKeys(res3.cookie, ['keystone.sid', '_interaction', '_interaction.sig', '_interaction_resume', '_interaction_resume.sig', '_session', '_session.sig']) + + // 3) callback to server side ( callback with code to oidc app site; server get the access and cann use it ) + + const params = serverSideOidcClient.callbackParams(res3.headers.location.replace(`${uri}#`, `${uri}?`)) + expect(new Set(Object.keys(params))).toEqual(new Set(['access_token', 'id_token', 'expires_in', 'token_type'])) + expect(params.access_token).toHaveLength(43) // important to be 43! + expect(params.token_type).toEqual('Bearer') + expect(params.id_token.length > 10).toBeTruthy() + + // 4) check requests by accessToken to /oidc/userinfo + + const userinfo = await serverSideOidcClient.userinfo(params.access_token) + expect(userinfo).toEqual({ + 'sub': c.user.id, + 'type': 'staff', + 'v': 1, + 'dv': 1, + 'isAdmin': false, + 'isSupport': false, + 'name': c.user.name, + }) + expect(await getAccessToken(params.access_token, c)).toMatchObject({ + 'accountId': c.user.id, + 'clientId': clientId, + 'expiresWithSession': true, + 'gty': 'implicit', + 'kind': 'AccessToken', + 'scope': 'openid', + }) + + // 5) check requests by accessToken to /admin/api + + await expectToGetAuthenticatedUser(`${c.serverUrl}/admin/api`, null) + await expectToGetAuthenticatedUser( + `${c.serverUrl}/admin/api`, + { id: c.user.id, name: c.user.name }, + { 'Authorization': `Bearer ${params.access_token}` }, + ) + + // 6) using of refresh token to throw error + await expect(serverSideOidcClient.refresh(params.access_token)).rejects.toThrow('unauthorized_client (requested grant type is not allowed for this client)') }) - expect(await getAccessToken(params.access_token, c)).toMatchObject({ - 'accountId': c.user.id, - 'clientId': clientId, - 'expiresWithSession': true, - 'gty': 'implicit', - 'kind': 'AccessToken', - 'scope': 'openid', - }) - - // 5) check requests by accessToken to /admin/api - - await expectToGetAuthenticatedUser(`${c.serverUrl}/admin/api`, null) - await expectToGetAuthenticatedUser( - `${c.serverUrl}/admin/api`, - { id: c.user.id, name: c.user.name }, - { 'Authorization': `Bearer ${params.access_token}` }, - ) - - // 6) using of refresh token to throw error - await expect(serverSideOidcClient.refresh(params.access_token)).rejects.toThrow('unauthorized_client (requested grant type is not allowed for this client)') - }) - test('case: code flow with response_type for implicit flow', async () => { - const uri = 'https://jwt.io/' - const clientId = getRandomString() - const clientSecret = getRandomString() - const admin = await makeLoggedInAdminClient() + test('case: code flow with response_type for implicit flow', async () => { + const uri = 'https://jwt.io/' + const clientId = getRandomString() + const clientSecret = getRandomString() + const admin = await makeLoggedInAdminClient() - await createTestOidcClient(admin, { - payload: { + await createTestOidcClient(admin, { + payload: { // application_type, client_id, client_name, client_secret, client_uri, contacts, default_acr_values, default_max_age, grant_types, id_token_signed_response_alg, initiate_login_uri, jwks, jwks_uri, logo_uri, policy_uri, post_logout_redirect_uris, redirect_uris, require_auth_time, response_types, scope, sector_identifier_uri, subject_type, token_endpoint_auth_method, tos_uri, userinfo_signed_response_alg + client_id: clientId, + client_secret: clientSecret, + redirect_uris: [uri], // using uri as redirect_uri to show the ID Token contents + response_types: ['code id_token', 'code', 'id_token'], + grant_types: ['implicit', 'authorization_code', 'refresh_token'], // 'implicit', 'authorization_code', 'refresh_token', or 'urn:ietf:params:oauth:grant-type:device_code' + token_endpoint_auth_method: 'client_secret_basic', + }, + }) + + const c = await makeClientWithNewRegisteredAndLoggedInUser() + expectCookieKeys(c.getCookie(), ['keystone.sid']) + + // 1) server side ( create oidc client and prepare oidcAuthUrl ) + + const oidcIssuer = await Issuer.discover(`${c.serverUrl}/oidc`) + const serverSideOidcClient = new oidcIssuer.Client({ client_id: clientId, client_secret: clientSecret, redirect_uris: [uri], // using uri as redirect_uri to show the ID Token contents - response_types: ['code id_token', 'code', 'id_token'], - grant_types: ['implicit', 'authorization_code', 'refresh_token'], // 'implicit', 'authorization_code', 'refresh_token', or 'urn:ietf:params:oauth:grant-type:device_code' + response_types: ['id_token token'], token_endpoint_auth_method: 'client_secret_basic', - }, - }) + }) - const c = await makeClientWithNewRegisteredAndLoggedInUser() - expectCookieKeys(c.getCookie(), ['keystone.sid']) + const nonce = generators.nonce() + const url = new URL(serverSideOidcClient.authorizationUrl({ nonce })) + const oidcAuthUrl = `${c.serverUrl}${url.pathname}${url.search}` - // 1) server side ( create oidc client and prepare oidcAuthUrl ) + // 2) client side ( open oidcAuthUrl ) - const oidcIssuer = await Issuer.discover(`${c.serverUrl}/oidc`) - const serverSideOidcClient = new oidcIssuer.Client({ - client_id: clientId, - client_secret: clientSecret, - redirect_uris: [uri], // using uri as redirect_uri to show the ID Token contents - response_types: ['id_token token'], - token_endpoint_auth_method: 'client_secret_basic', + const res1 = await request(oidcAuthUrl, c.getCookie()) + console.log(res1) + expect(res1.status).toBe(303) + expect(res1.headers.location.startsWith(`${uri}#error`)).toBeTruthy() + expect(res1.headers.location).toContain('error_description=requested%20response_type%20is%20not%20allowed%20for%20this%20client') + expectCookieKeys(res1.cookie, ['keystone.sid']) }) - const nonce = generators.nonce() - const url = new URL(serverSideOidcClient.authorizationUrl({ nonce })) - const oidcAuthUrl = `${c.serverUrl}${url.pathname}${url.search}` - - // 2) client side ( open oidcAuthUrl ) - - const res1 = await request(oidcAuthUrl, c.getCookie()) - console.log(res1) - expect(res1.status).toBe(303) - expect(res1.headers.location.startsWith(`${uri}#error`)).toBeTruthy() - expect(res1.headers.location).toContain('error_description=requested%20response_type%20is%20not%20allowed%20for%20this%20client') - expectCookieKeys(res1.cookie, ['keystone.sid']) - }) - - test('miniapp server side oidc client logic (code flow)', async () => { - const c = await makeClientWithNewRegisteredAndLoggedInUser() - const admin = await makeLoggedInAdminClient() + test('miniapp server side oidc client logic (code flow)', async () => { + const c = await makeClientWithNewRegisteredAndLoggedInUser() + const admin = await makeLoggedInAdminClient() - const uri = 'https://jwt.io/' - const clientId = getRandomString() - const clientSecret = getRandomString() + const uri = 'https://jwt.io/' + const clientId = getRandomString() + const clientSecret = getRandomString() - await createTestOidcClient(admin, { - payload: { + await createTestOidcClient(admin, { + payload: { // application_type, client_id, client_name, client_secret, client_uri, contacts, default_acr_values, default_max_age, grant_types, id_token_signed_response_alg, initiate_login_uri, jwks, jwks_uri, logo_uri, policy_uri, post_logout_redirect_uris, redirect_uris, require_auth_time, response_types, scope, sector_identifier_uri, subject_type, token_endpoint_auth_method, tos_uri, userinfo_signed_response_alg + client_id: clientId, + client_secret: clientSecret, + redirect_uris: [uri], // using uri as redirect_uri to show the ID Token contents + response_types: ['code id_token', 'code', 'id_token'], + grant_types: ['implicit', 'authorization_code', 'refresh_token'], // 'implicit', 'authorization_code', 'refresh_token', or 'urn:ietf:params:oauth:grant-type:device_code' + token_endpoint_auth_method: 'client_secret_basic', + }, + }) + + const serverUrl = c.serverUrl + const issuer = new Issuer({ + authorization_endpoint: `${serverUrl}/oidc/auth`, + token_endpoint: `${serverUrl}/oidc/token`, + end_session_endpoint: `${serverUrl}/oidc/session/end`, + jwks_uri: `${serverUrl}/oidc/jwks`, + revocation_endpoint: `${serverUrl}/oidc/token/revocation`, + userinfo_endpoint: `${serverUrl}/oidc/me`, + issuer: serverUrl, + }) + const client = new issuer.Client({ client_id: clientId, client_secret: clientSecret, redirect_uris: [uri], // using uri as redirect_uri to show the ID Token contents - response_types: ['code id_token', 'code', 'id_token'], - grant_types: ['implicit', 'authorization_code', 'refresh_token'], // 'implicit', 'authorization_code', 'refresh_token', or 'urn:ietf:params:oauth:grant-type:device_code' + response_types: ['code'], token_endpoint_auth_method: 'client_secret_basic', - }, - }) - - const serverUrl = c.serverUrl - const issuer = new Issuer({ - authorization_endpoint: `${serverUrl}/oidc/auth`, - token_endpoint: `${serverUrl}/oidc/token`, - end_session_endpoint: `${serverUrl}/oidc/session/end`, - jwks_uri: `${serverUrl}/oidc/jwks`, - revocation_endpoint: `${serverUrl}/oidc/token/revocation`, - userinfo_endpoint: `${serverUrl}/oidc/me`, - issuer: serverUrl, - }) - const client = new issuer.Client({ - client_id: clientId, - client_secret: clientSecret, - redirect_uris: [uri], // using uri as redirect_uri to show the ID Token contents - response_types: ['code'], - token_endpoint_auth_method: 'client_secret_basic', - }) - const _validateJWT = client.validateJWT - client.validateJWT = async (jwt, expectedAlg, required) => { - try { - await _validateJWT.call(client, jwt, expectedAlg, required) - } catch (error) { - console.error({ message: error.message, jwt, error }) + }) + const _validateJWT = client.validateJWT + client.validateJWT = async (jwt, expectedAlg, required) => { + try { + await _validateJWT.call(client, jwt, expectedAlg, required) + } catch (error) { + console.error({ message: error.message, jwt, error }) + } + return { protected: jwtDecode(jwt, { header: true }), payload: jwtDecode(jwt) } } - return { protected: jwtDecode(jwt, { header: true }), payload: jwtDecode(jwt) } - } - const checks = { nonce: generators.nonce(), state: generators.state() } - const redirectUrl = client.authorizationUrl({ - response_type: 'code', - ...checks, - }) + const checks = { nonce: generators.nonce(), state: generators.state() } + const redirectUrl = client.authorizationUrl({ + response_type: 'code', + ...checks, + }) - console.log(redirectUrl) - const res1 = await request(redirectUrl, c.getCookie(), 100) - expect(res1.status).toEqual(200) + console.log(redirectUrl) + const res1 = await request(redirectUrl, c.getCookie(), 100) + expect(res1.status).toEqual(200) - console.log(res1.url) - const params = client.callbackParams(res1.url) - const tokenSet = await client.callback(uri, { code: params.code }, { nonce: checks.nonce }) - expect(tokenSet.access_token).toBeTruthy() + console.log(res1.url) + const params = client.callbackParams(res1.url) + const tokenSet = await client.callback(uri, { code: params.code }, { nonce: checks.nonce }) + expect(tokenSet.access_token).toBeTruthy() + }) }) }) diff --git a/apps/condo/domains/user/utils/testSchema/index.js b/apps/condo/domains/user/utils/testSchema/index.js index 4e1d9371ed4..c3db8489c62 100644 --- a/apps/condo/domains/user/utils/testSchema/index.js +++ b/apps/condo/domains/user/utils/testSchema/index.js @@ -426,6 +426,7 @@ async function createTestOidcClient (client, extraAttrs = {}) { dv: 1, sender, clientId, + isEnabled: true, payload: { client_id: clientId, grant_types: ['implicit', 'authorization_code', 'refresh_token'], diff --git a/apps/condo/migrations/20240221230519-0368_oidcclient_isenabled_and_more.js b/apps/condo/migrations/20240221230519-0368_oidcclient_isenabled_and_more.js new file mode 100644 index 00000000000..306c0f80d1e --- /dev/null +++ b/apps/condo/migrations/20240221230519-0368_oidcclient_isenabled_and_more.js @@ -0,0 +1,35 @@ +// auto generated by kmigrator +// KMIGRATOR:0368_oidcclient_isenabled_and_more:IyBHZW5lcmF0ZWQgYnkgRGphbmdvIDQuMS41IG9uIDIwMjQtMDItMjEgMTg6MDcKCmZyb20gZGphbmdvLmRiIGltcG9ydCBtaWdyYXRpb25zLCBtb2RlbHMKCgpjbGFzcyBNaWdyYXRpb24obWlncmF0aW9ucy5NaWdyYXRpb24pOgoKICAgIGRlcGVuZGVuY2llcyA9IFsKICAgICAgICAoJ19kamFuZ29fc2NoZW1hJywgJzAzNjdfbm90aWZpY2F0aW9uYW5vbnltb3Vzc2V0dGluZ2hpc3RvcnlyZWNvcmRfYW5kX21vcmUnKSwKICAgIF0KCiAgICBvcGVyYXRpb25zID0gWwogICAgICAgIG1pZ3JhdGlvbnMuQWRkRmllbGQoCiAgICAgICAgICAgIG1vZGVsX25hbWU9J29pZGNjbGllbnQnLAogICAgICAgICAgICBuYW1lPSdpc0VuYWJsZWQnLAogICAgICAgICAgICBmaWVsZD1tb2RlbHMuQm9vbGVhbkZpZWxkKGRlZmF1bHQ9VHJ1ZSksCiAgICAgICAgICAgIHByZXNlcnZlX2RlZmF1bHQ9RmFsc2UsCiAgICAgICAgKSwKICAgICAgICBtaWdyYXRpb25zLkFkZEZpZWxkKAogICAgICAgICAgICBtb2RlbF9uYW1lPSdvaWRjY2xpZW50aGlzdG9yeXJlY29yZCcsCiAgICAgICAgICAgIG5hbWU9J2lzRW5hYmxlZCcsCiAgICAgICAgICAgIGZpZWxkPW1vZGVscy5Cb29sZWFuRmllbGQoYmxhbms9VHJ1ZSwgbnVsbD1UcnVlKSwKICAgICAgICApLAogICAgXQo= + +exports.up = async (knex) => { + await knex.raw(` + BEGIN; +-- +-- Add field isEnabled to oidcclient +-- +ALTER TABLE "OidcClient" ADD COLUMN "isEnabled" boolean DEFAULT true NOT NULL; +ALTER TABLE "OidcClient" ALTER COLUMN "isEnabled" DROP DEFAULT; +-- +-- Add field isEnabled to oidcclienthistoryrecord +-- +ALTER TABLE "OidcClientHistoryRecord" ADD COLUMN "isEnabled" boolean NULL; +COMMIT; + + `) +} + +exports.down = async (knex) => { + await knex.raw(` + BEGIN; +-- +-- Add field isEnabled to oidcclienthistoryrecord +-- +ALTER TABLE "OidcClientHistoryRecord" DROP COLUMN "isEnabled" CASCADE; +-- +-- Add field isEnabled to oidcclient +-- +ALTER TABLE "OidcClient" DROP COLUMN "isEnabled" CASCADE; +COMMIT; + + `) +} diff --git a/apps/condo/schema.graphql b/apps/condo/schema.graphql index cece5744d06..8e9e4e22733 100644 --- a/apps/condo/schema.graphql +++ b/apps/condo/schema.graphql @@ -1818,6 +1818,7 @@ type OidcClientHistoryRecord { clientId: String payload: JSON name: String + isEnabled: Boolean meta: JSON expiresAt: String id: ID! @@ -1878,6 +1879,8 @@ input OidcClientHistoryRecordWhereInput { name_not_ends_with_i: String name_in: [String] name_not_in: [String] + isEnabled: Boolean + isEnabled_not: Boolean meta: JSON meta_not: JSON meta_in: [JSON] @@ -1977,6 +1980,8 @@ enum SortOidcClientHistoryRecordsBy { clientId_DESC name_ASC name_DESC + isEnabled_ASC + isEnabled_DESC expiresAt_ASC expiresAt_DESC id_ASC @@ -2001,6 +2006,7 @@ input OidcClientHistoryRecordUpdateInput { clientId: String payload: JSON name: String + isEnabled: Boolean meta: JSON expiresAt: String v: Int @@ -2026,6 +2032,7 @@ input OidcClientHistoryRecordCreateInput { clientId: String payload: JSON name: String + isEnabled: Boolean meta: JSON expiresAt: String v: Int @@ -2066,6 +2073,10 @@ type OidcClient { """ The human readable name for client """ name: String + """ A switch that allows you to disable some OIDC clients. If an OIDC client is disabled, it cannot be used for OIDC authorization. Used mainly by developers portal to create OIDC client before publishing an application, and enable OIDC after publishing. + """ + isEnabled: Boolean + """ The additional client data """ meta: JSON @@ -2143,6 +2154,8 @@ input OidcClientWhereInput { name_not_ends_with_i: String name_in: [String] name_not_in: [String] + isEnabled: Boolean + isEnabled_not: Boolean meta: JSON meta_not: JSON meta_in: [JSON] @@ -2258,6 +2271,8 @@ enum SortOidcClientsBy { clientId_DESC name_ASC name_DESC + isEnabled_ASC + isEnabled_DESC expiresAt_ASC expiresAt_DESC id_ASC @@ -2286,6 +2301,7 @@ input OidcClientUpdateInput { clientId: String payload: JSON name: String + isEnabled: Boolean meta: JSON expiresAt: String v: Int @@ -2310,6 +2326,7 @@ input OidcClientCreateInput { clientId: String payload: JSON name: String + isEnabled: Boolean meta: JSON expiresAt: String v: Int diff --git a/apps/condo/schema.ts b/apps/condo/schema.ts index a4915cf2c3c..631da9c575f 100644 --- a/apps/condo/schema.ts +++ b/apps/condo/schema.ts @@ -52301,6 +52301,8 @@ export type OidcClient = { payload?: Maybe; /** The human readable name for client */ name?: Maybe; + /** A switch that allows you to disable some OIDC clients. If an OIDC client is disabled, it cannot be used for OIDC authorization. Used mainly by developers portal to create OIDC client before publishing an application, and enable OIDC after publishing. */ + isEnabled?: Maybe; /** The additional client data */ meta?: Maybe; /** The timestamp of the client expiration */ @@ -52329,6 +52331,7 @@ export type OidcClientCreateInput = { clientId?: Maybe; payload?: Maybe; name?: Maybe; + isEnabled?: Maybe; meta?: Maybe; expiresAt?: Maybe; v?: Maybe; @@ -52358,6 +52361,7 @@ export type OidcClientHistoryRecord = { clientId?: Maybe; payload?: Maybe; name?: Maybe; + isEnabled?: Maybe; meta?: Maybe; expiresAt?: Maybe; id: Scalars['ID']; @@ -52379,6 +52383,7 @@ export type OidcClientHistoryRecordCreateInput = { clientId?: Maybe; payload?: Maybe; name?: Maybe; + isEnabled?: Maybe; meta?: Maybe; expiresAt?: Maybe; v?: Maybe; @@ -52405,6 +52410,7 @@ export type OidcClientHistoryRecordUpdateInput = { clientId?: Maybe; payload?: Maybe; name?: Maybe; + isEnabled?: Maybe; meta?: Maybe; expiresAt?: Maybe; v?: Maybe; @@ -52464,6 +52470,8 @@ export type OidcClientHistoryRecordWhereInput = { name_not_ends_with_i?: Maybe; name_in?: Maybe>>; name_not_in?: Maybe>>; + isEnabled?: Maybe; + isEnabled_not?: Maybe; meta?: Maybe; meta_not?: Maybe; meta_in?: Maybe>>; @@ -52571,6 +52579,7 @@ export type OidcClientUpdateInput = { clientId?: Maybe; payload?: Maybe; name?: Maybe; + isEnabled?: Maybe; meta?: Maybe; expiresAt?: Maybe; v?: Maybe; @@ -52629,6 +52638,8 @@ export type OidcClientWhereInput = { name_not_ends_with_i?: Maybe; name_in?: Maybe>>; name_not_in?: Maybe>>; + isEnabled?: Maybe; + isEnabled_not?: Maybe; meta?: Maybe; meta_not?: Maybe; meta_in?: Maybe>>; @@ -77486,6 +77497,8 @@ export enum SortOidcClientHistoryRecordsBy { ClientIdDesc = 'clientId_DESC', NameAsc = 'name_ASC', NameDesc = 'name_DESC', + IsEnabledAsc = 'isEnabled_ASC', + IsEnabledDesc = 'isEnabled_DESC', ExpiresAtAsc = 'expiresAt_ASC', ExpiresAtDesc = 'expiresAt_DESC', IdAsc = 'id_ASC', @@ -77511,6 +77524,8 @@ export enum SortOidcClientsBy { ClientIdDesc = 'clientId_DESC', NameAsc = 'name_ASC', NameDesc = 'name_DESC', + IsEnabledAsc = 'isEnabled_ASC', + IsEnabledDesc = 'isEnabled_DESC', ExpiresAtAsc = 'expiresAt_ASC', ExpiresAtDesc = 'expiresAt_DESC', IdAsc = 'id_ASC',