Skip to content

Commit

Permalink
SSO - standard realm updates
Browse files Browse the repository at this point in the history
remove keycloak from API, replace with jwt verification only.
token to user mapping now in configuration not in keycloak.
frontend keycloak configuration changes slightly (no resource_access).
no user role.

Signed-off-by: Jason Sherman <[email protected]>
  • Loading branch information
usingtechnology committed Feb 26, 2024
1 parent 734bce2 commit 879f6b5
Show file tree
Hide file tree
Showing 53 changed files with 749 additions and 615 deletions.
22 changes: 14 additions & 8 deletions app/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ const path = require('path');
const Problem = require('api-problem');
const querystring = require('querystring');

const keycloak = require('./src/components/keycloak');
const log = require('./src/components/log')(module.filename);
const httpLogger = require('./src/components/log').httpLogger;
const middleware = require('./src/forms/common/middleware');
Expand Down Expand Up @@ -40,9 +39,6 @@ if (process.env.NODE_ENV !== 'test') {
app.use(httpLogger);
}

// Use Keycloak OIDC Middleware
app.use(keycloak.middleware());

// Block requests until service is ready
app.use((_req, res, next) => {
if (state.shutdown) {
Expand Down Expand Up @@ -178,11 +174,16 @@ function initializeConnections() {
.then((results) => {
state.connections.data = results[0];

if (state.connections.data) log.info('DataConnection Reachable', { function: 'initializeConnections' });
if (state.connections.data)
log.info('DataConnection Reachable', {
function: 'initializeConnections',
});
})
.catch((error) => {
log.error(`Initialization failed: Database OK = ${state.connections.data}`, { function: 'initializeConnections' });
log.error('Connection initialization failure', error.message, { function: 'initializeConnections' });
log.error('Connection initialization failure', error.message, {
function: 'initializeConnections',
});
if (!state.ready) {
process.exitCode = 1;
shutdown();
Expand All @@ -191,7 +192,9 @@ function initializeConnections() {
.finally(() => {
state.ready = Object.values(state.connections).every((x) => x);
if (state.ready) {
log.info('Service ready to accept traffic', { function: 'initializeConnections' });
log.info('Service ready to accept traffic', {
function: 'initializeConnections',
});
// Start periodic 10 second connection probe check
probeId = setInterval(checkConnections, 10000);
}
Expand All @@ -211,7 +214,10 @@ function checkConnections() {
Promise.all(tasks).then((results) => {
state.connections.data = results[0];
state.ready = Object.values(state.connections).every((x) => x);
if (!wasReady && state.ready) log.info('Service ready to accept traffic', { function: 'checkConnections' });
if (!wasReady && state.ready)
log.info('Service ready to accept traffic', {
function: 'checkConnections',
});
log.verbose(state);
if (!state.ready) {
process.exitCode = 1;
Expand Down
13 changes: 7 additions & 6 deletions app/config/custom-environment-variables.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,19 +35,20 @@
"keycloak": {
"clientId": "FRONTEND_KC_CLIENTID",
"realm": "FRONTEND_KC_REALM",
"serverUrl": "FRONTEND_KC_SERVERURL"
"serverUrl": "FRONTEND_KC_SERVERURL",
"logoutUrl": "FRONTEND_KC_LOGOUTURL"
}
},
"server": {
"apiPath": "SERVER_APIPATH",
"basePath": "SERVER_BASEPATH",
"bodyLimit": "SERVER_BODYLIMIT",
"keycloak": {
"clientId": "SERVER_KC_CLIENTID",
"clientSecret": "SERVER_KC_CLIENTSECRET",
"publicKey": "SERVER_KC_PUBLICKEY",
"realm": "SERVER_KC_REALM",
"serverUrl": "SERVER_KC_SERVERURL"
"serverUrl": "SERVER_KC_SERVERURL",
"jwksUri": "SERVER_KC_JWKSURI",
"issuer": "SERVER_KC_ISSUER",
"audience": "SERVER_KC_AUDIENCE",
"maxTokenAge": "SERVER_KC_MAXTOKENAGE"
},
"logFile": "SERVER_LOGFILE",
"logLevel": "SERVER_LOGLEVEL",
Expand Down
14 changes: 9 additions & 5 deletions app/config/default.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,18 +32,22 @@
"basePath": "/app",
"keycloak": {
"clientId": "chefs-frontend",
"realm": "chefs",
"serverUrl": "https://dev.loginproxy.gov.bc.ca/auth"
"realm": "standard",
"serverUrl": "https://dev.loginproxy.gov.bc.ca/auth",
"logoutUrl": "https://logon7.gov.bc.ca/clp-cgi/logoff.cgi?retnow=1&returl=https%3A%2F%2Fdev.loginproxy.gov.bc.ca%2Fauth%2Frealms%2Fstandard%2Fprotocol%2Fopenid-connect%2Flogout%3Fpost_logout_redirect_uri%3Dhttp%3A%2F%2Flocalhost%3A5173%2Fapp%26client_id%3Dchefs-frontend"
}
},
"server": {
"apiPath": "/api/v1",
"basePath": "/app",
"bodyLimit": "30mb",
"keycloak": {
"clientId": "chefs",
"realm": "chefs",
"serverUrl": "https://dev.loginproxy.gov.bc.ca/auth"
"realm": "standard",
"serverUrl": "https://dev.loginproxy.gov.bc.ca/auth",
"jwksUri": "https://dev.loginproxy.gov.bc.ca/auth/realms/standard/protocol/openid-connect/certs",
"issuer": "https://dev.loginproxy.gov.bc.ca/auth/realms/standard",
"audience": "chefs-frontend",
"maxTokenAge": "300"
},
"logLevel": "http",
"port": "8080",
Expand Down
59 changes: 21 additions & 38 deletions app/frontend/src/components/base/BaseSecure.vue
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ export default {
'authenticated',
'identityProvider',
'isAdmin',
'isUser',
'ready',
]),
...mapState(useFormStore, ['lang']),
Expand All @@ -44,48 +43,32 @@ export default {

<template>
<div v-if="authenticated">
<div v-if="isUser">
<div v-if="admin && !isAdmin" class="text-center">
<h1 class="my-8" :lang="lang">
{{ $t('trans.baseSecure.401UnAuthorized') }}
</h1>
<p :lang="lang">
{{ $t('trans.baseSecure.401UnAuthorizedErrMsg') }}
</p>
</div>
<div
v-else-if="permission && !hasPermission(identityProvider, permission)"
class="text-center"
>
<h1 class="my-8" :lang="lang">
{{ $t('trans.baseSecure.403Forbidden') }}
</h1>
<p :lang="lang">
{{
$t('trans.baseSecure.403ErrorMsg', {
idp: permission,
})
}}
</p>
</div>
<slot v-else />
</div>
<!-- TODO: Figure out better way to alert when user lacks chefs user role -->
<div v-else class="text-center">
<div v-if="admin && !isAdmin" class="text-center">
<h1 class="my-8" :lang="lang">
{{ $t('trans.baseSecure.401UnAuthorized') }}
</h1>
<p>
<span :lang="lang" v-html="$t('trans.baseSecure.401ErrorMsg')"> </span>
<a :href="mailToLink">{{ contactInfo }}</a>
<p :lang="lang">
{{ $t('trans.baseSecure.401UnAuthorizedErrMsg') }}
</p>
</div>
<div
v-else-if="
permission && !hasPermission(identityProvider?.code, permission)
"
class="text-center"
>
<h1 class="my-8" :lang="lang">
{{ $t('trans.baseSecure.403Forbidden') }}
</h1>
<p :lang="lang">
{{
$t('trans.baseSecure.403ErrorMsg', {
idp: permission,
})
}}
</p>
<router-link :to="{ name: 'About' }">
<v-btn color="primary" class="about-btn" size="large">
<v-icon start icon="mdi:mdi-home"></v-icon>
<span :lang="lang">{{ $t('trans.baseSecure.about') }}</span>
</v-btn>
</router-link>
</div>
<slot v-else />
</div>
<div v-else class="text-center">
<h1 class="my-8" :lang="lang">
Expand Down
2 changes: 1 addition & 1 deletion app/frontend/src/components/bcgov/BCGovNavBar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export default {
return this.$route && this.$route.meta && this.$route.meta.formSubmitMode;
},
hasPrivileges() {
return this.isPrimary(this.identityProvider);
return this.isPrimary(this.identityProvider?.code);
},
},
};
Expand Down
2 changes: 1 addition & 1 deletion app/frontend/src/components/designer/FormsTable.vue
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ export default {
];
},
canCreateForm() {
return this.isPrimary(this.user.idp);
return this.isPrimary(this.user.idp?.code);
},
filteredFormList() {
return this.formList.filter(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,11 @@ export default {
return IdentityMode;
},
},
mounted() {
if (this.form?.idps && this.form.idps.length) {
this.idpType = this.form.idps[0];
}
},
methods: {
userTypeChanged() {
// if they checked enable drafts then went back to public, uncheck it
Expand Down Expand Up @@ -129,12 +134,12 @@ export default {
>
<v-radio
v-for="button in loginButtons"
:key="button.type"
:value="button.type"
:key="button.code"
:value="button.code"
class="mx-2"
>
<template #label>
<span :class="{ 'mr-2': isRTL }"> {{ button.label }} </span>
<span :class="{ 'mr-2': isRTL }"> {{ button.display }} </span>
</template>
</v-radio>
<!-- Mandatory BCeID process notification -->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export default {
return IdentityMode;
},
primaryIdpUser() {
return this.isPrimary(this.identityProvider);
return this.isPrimary(this.identityProvider?.code);
},
},
methods: {
Expand Down
2 changes: 1 addition & 1 deletion app/frontend/src/components/forms/SubmissionsTable.vue
Original file line number Diff line number Diff line change
Expand Up @@ -427,7 +427,7 @@ export default {
}),
deletedOnly: this.deletedOnly,
createdBy: this.currentUserOnly
? `${this.user.username}@${this.user.idp}`
? `${this.user.username}@${this.user.idp?.code}`
: '',
};
await this.fetchSubmissions(criteria);
Expand Down
4 changes: 2 additions & 2 deletions app/frontend/src/components/forms/manage/AddTeamMember.vue
Original file line number Diff line number Diff line change
Expand Up @@ -174,8 +174,8 @@ export default {
>
<v-radio
v-for="button in loginButtons"
:key="button.type"
:value="button.type"
:key="button.code"
:value="button.code"
:label="button.display"
/>
</v-radio-group>
Expand Down
7 changes: 4 additions & 3 deletions app/frontend/src/components/forms/manage/TeamManagement.vue
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ export default {
methods: {
...mapActions(useFormStore, ['fetchForm', 'getFormPermissionsForUser']),
...mapActions(useNotificationStore, ['addNotification']),
...mapActions(useIdpStore, ['findByCode']),
async loadItems() {
this.loading = true;
Expand Down Expand Up @@ -162,7 +163,7 @@ export default {
roles: '*',
});
this.formUsers = formUsersResponse?.data?.map((user) => {
user.idp = user.user_idpCode;
user.idp = this.findByCode(user.user_idpCode);
return user;
});
} catch (error) {
Expand All @@ -185,7 +186,7 @@ export default {
fullName: user.fullName,
userId: user.userId,
username: user.username,
identityProvider: user.idp,
identityProvider: user.idp?.code,
};
this.roleList
.map((role) => role.code)
Expand All @@ -201,7 +202,7 @@ export default {
)
return true;
// if the header isn't in the IDPs roles, then disable
const idpRoles = this.listRoles(user.identityProvider);
const idpRoles = this.listRoles(user.identityProvider?.code);
return idpRoles && !idpRoles.includes(header);
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ export default {
if (!input) return;
this.isLoadingDropdown = true;
try {
// The form's IDP (only support 1 at a time right now), blank is 'team' and should be IDIR
// The form's IDP (only support 1 at a time right now), blank is 'team' and should be Primary
let params = {};
params.idpCode = this.selectedIdp;
let teamMembershipConfig = this.teamMembershipSearch(this.selectedIdp);
Expand Down Expand Up @@ -242,8 +242,8 @@ export default {
<v-radio-group v-if="isDraft" v-model="selectedIdp" inline>
<v-radio
v-for="button in loginButtons"
:key="button.type"
:value="button.type"
:key="button.code"
:value="button.code"
:label="button.display"
/>
</v-radio-group>
Expand Down
10 changes: 5 additions & 5 deletions app/frontend/src/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,8 @@ async function loadConfig() {
!config.keycloak ||
!config.keycloak.clientId ||
!config.keycloak.realm ||
!config.keycloak.serverUrl
!config.keycloak.serverUrl ||
!config.keycloak.logoutUrl
) {
throw new Error('Keycloak is misconfigured');
}
Expand All @@ -165,7 +166,7 @@ function loadKeycloak(config) {
};

const options = Object.assign({}, defaultParams, {
init: { onLoad: 'check-sso' },
init: { pkceMethod: 'S256', checkLoginIframe: false, onLoad: 'check-sso' },
config: {
clientId: config.keycloak.clientId,
realm: config.keycloak.realm,
Expand All @@ -188,6 +189,7 @@ function loadKeycloak(config) {
const ctor = sanitizeConfig(cfg);

const authStore = useAuthStore();
authStore.logoutUrl = config.keycloak.logoutUrl;

keycloak = new Keycloak(ctor);
keycloak.onReady = (authenticated) => {
Expand All @@ -207,9 +209,7 @@ function loadKeycloak(config) {
);
authStore.logoutFn = () => {
clearInterval(updateTokenInterval);
keycloak.logout(
options.logout || { redirectUri: config['logoutRedirectUri'] }
);
authStore.updateKeycloak(keycloak, false);
};
};
keycloak.onAuthRefreshSuccess = () => {
Expand Down
Loading

0 comments on commit 879f6b5

Please sign in to comment.