Skip to content

Commit

Permalink
oidc login flow updates
Browse files Browse the repository at this point in the history
  • Loading branch information
future-pirate-king committed Feb 16, 2024
1 parent 3669be2 commit 62e1ad8
Show file tree
Hide file tree
Showing 24 changed files with 984 additions and 35 deletions.
73 changes: 45 additions & 28 deletions app/authenticators/irene.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
/* eslint-disable prettier/prettier, ember/no-get */
/* eslint-disable ember/no-get */
import Base from 'ember-simple-auth/authenticators/base';
import ENV from 'irene/config/environment';
import { inject as service } from '@ember/service';
import { getOwner } from '@ember/application';


const b64EncodeUnicode = str =>
btoa(encodeURIComponent(str).replace(/%([0-9A-F]{2})/g, (match, p1) => String.fromCharCode(`0x${p1}`))
)
;

const b64EncodeUnicode = (str) =>
btoa(
encodeURIComponent(str).replace(/%([0-9A-F]{2})/g, (match, p1) =>
String.fromCharCode(`0x${p1}`)
)
);
const getB64Token = (user, token) => b64EncodeUnicode(`${user}:${token}`);

const processData = (data) => {
Expand All @@ -18,55 +18,72 @@ const processData = (data) => {
};

const IreneAuthenticator = Base.extend({

ajax: service(),
router: service(),
window: service('browser/window'),

resumeTransistion() {
const authenticatedRoute = getOwner(this).lookup("route:authenticated");
const lastTransition = authenticatedRoute.get("lastTransition");
const authenticatedRoute = getOwner(this).lookup('route:authenticated');
const lastTransition = authenticatedRoute.get('lastTransition');

if (lastTransition) {
return lastTransition.retry();
} else {
const applicationRoute = getOwner(this).lookup("route:application");
return applicationRoute.transitionTo(ENV['ember-simple-auth']["routeAfterAuthentication"]);
const applicationRoute = getOwner(this).lookup('route:application');
return applicationRoute.transitionTo(
ENV['ember-simple-auth']['routeAfterAuthentication']
);
}
},

async checkAndPerformFrdeskRedirect(username) {
const window = this.get('window');
const queryParams = this.get('router')?.currentRoute?.queryParams;

if (queryParams?.next) {
const nextRoute = `${queryParams.next}&username=${username}`;
window.location = nextRoute;

return;
}
},

async authenticate(identification, password, otp) {
const ajax = this.get("ajax");
const ajax = this.get('ajax');
const data = {
username: identification,
password,
otp
}
otp,
};
const url = ENV['ember-simple-auth']['loginEndPoint'];
return ajax.post(url, { data })
.then(data => {
data = processData(data);
this.resumeTransistion();
return data;
});
return ajax.post(url, { data }).then((data) => {
data = processData(data);

this.checkAndPerformFrdeskRedirect(identification);
this.resumeTransistion(identification);

return data;
});
},

async restore(data) {
const ajax = this.get("ajax");
const ajax = this.get('ajax');
const url = ENV['ember-simple-auth']['checkEndPoint'];
await ajax.post(url, {
data: {},
headers: {
'Authorization': `Basic ${data.b64token}`
}
})
Authorization: `Basic ${data.b64token}`,
},
});
return data;
},

async invalidate() {
const ajax = this.get("ajax");
const ajax = this.get('ajax');
const url = ENV['ember-simple-auth']['logoutEndPoint'];
await ajax.post(url);
location.reload();
}
},
});


export default IreneAuthenticator;
69 changes: 69 additions & 0 deletions app/components/oidc-authorize/index.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
{{#if @data.form_data.authorization_needed}}
<div local-class='oidc-authorize-root'>
<div local-class='oidc-authorize-container'>
<div class='mb-6'>
<AuthAssets />
</div>

<div class='py-3' local-class='oidc-authorize-card'>
<AkTypography
data-test-oidcAuthorize-heading
class='px-3'
@variant='h6'
>
{{t
'oidcModule.permissionHeading'
applicationName=this.applicationName
}}
</AkTypography>

<div class='py-1 px-3'>
<AkList as |akl|>
{{#each this.scopeDescriptions as |sd|}}
<akl.listItem
data-test-oidcAuthorize-scopeDescription='{{sd}}'
as |li|
>
<li.leftIcon @disabled={{true}}>
<AkIcon @iconName='chevron-right' />
</li.leftIcon>

<li.text @primaryText={{sd}} />
</akl.listItem>
{{/each}}
</AkList>
</div>

<AkDivider class='mb-3' />

<AkStack
class='px-3'
@alignItems='center'
@justifyContent='space-between'
@spacing='1.5'
>
<AkButton
data-test-oidcAuthorize-cancelBtn
class='w-full'
@variant='outlined'
@color='neutral'
{{on 'click' (perform this.cancelAuthorization)}}
@disabled={{this.oidc.authorizeOidcAppPermissions.isRunning}}
>
{{t 'cancel'}}
</AkButton>

<AkButton
data-test-oidcAuthorize-authorizeBtn
class='w-full'
@disabled={{this.oidc.authorizeOidcAppPermissions.isRunning}}
@loading={{this.allowAuthorization.isRunning}}
{{on 'click' (perform this.allowAuthorization)}}
>
{{t 'authorize'}}
</AkButton>
</AkStack>
</div>
</div>
</div>
{{/if}}
22 changes: 22 additions & 0 deletions app/components/oidc-authorize/index.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
.oidc-authorize-root {
width: 100%;
height: 100vh;
display: flex;
justify-content: center;
background-color: var(--oidc-authorize-container-background-color);
overflow: auto;
padding: 3em;
box-sizing: border-box;

.oidc-authorize-container {
margin: auto;
}

.oidc-authorize-card {
width: 360px;
border-radius: 4px;
background-color: var(--oidc-authorize-card-background-color);
box-shadow: var(--oidc-authorize-card-box-shadow);
border: 1px solid var(--oidc-authorize-card-border-color);
}
}
63 changes: 63 additions & 0 deletions app/components/oidc-authorize/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import Component from '@glimmer/component';
import { inject as service } from '@ember/service';
import { task } from 'ember-concurrency';

import OidcService, { OidcAuthorizationResponse } from 'irene/services/oidc';

export interface OidcAuthorizeSignature {
Args: {
token?: string;
data?: OidcAuthorizationResponse;
};
}

export default class OidcAuthorizeComponent extends Component<OidcAuthorizeSignature> {
@service declare oidc: OidcService;

constructor(owner: unknown, args: OidcAuthorizeSignature['Args']) {
super(owner, args);

this.authorizeIfNoUserAuthorizationNeeded();
}

get applicationName() {
return this.args.data?.form_data?.application_name;
}

get scopeDescriptions() {
return this.args.data?.form_data?.scopes_descriptions;
}

authorizeIfNoUserAuthorizationNeeded() {
const formData = this.args.data?.form_data;

const authorizationNotNeeded =
typeof formData !== 'undefined' &&
formData !== null &&
!formData.authorization_needed;

if (authorizationNotNeeded) {
this.oidc.authorizeOidcAppPermissions.perform(this.args.token as string);
}
}

cancelAuthorization = task(async () => {
await this.oidc.authorizeOidcAppPermissions.perform(
this.args.token as string,
false
);
});

allowAuthorization = task(async () => {
await this.oidc.authorizeOidcAppPermissions.perform(
this.args.token as string,
true
);
});
}

declare module '@glint/environment-ember-loose/registry' {
export default interface Registry {
OidcAuthorize: typeof OidcAuthorizeComponent;
}
}
12 changes: 12 additions & 0 deletions app/controllers/oidc/error.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import Controller from '@ember/controller';
import { tracked } from '@glimmer/tracking';

export interface OidcError {
statusCode: number;
code?: string;
description?: string;
}

export default class OidcErrorController extends Controller {
@tracked error: OidcError | null = null;
}
5 changes: 5 additions & 0 deletions app/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@ Router.map(function () {
this.route('redirect');
});

this.route('oidc', { path: 'dashboard/oidc' }, function () {
this.route('redirect');
this.route('authorize');
});

this.route('register');

this.route('register-via-invite', {
Expand Down
4 changes: 4 additions & 0 deletions app/routes/authenticated.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import DatetimeService from 'irene/services/datetime';
import TrialService from 'irene/services/trial';
import IntegrationService from 'irene/services/integration';
import OrganizationService from 'irene/services/organization';
import OidcService from 'irene/services/oidc';
import UserModel from 'irene/models/user';
import { CSBMap } from 'irene/router';
import ENV from 'irene/config/environment';
Expand All @@ -29,13 +30,16 @@ export default class AuthenticatedRoute extends Route {
@service declare websocket: any;
@service declare integration: IntegrationService;
@service declare store: Store;
@service declare oidc: OidcService;
@service('notifications') declare notify: NotificationService;
@service('organization') declare org: OrganizationService;
@service('browser/window') declare window: Window;

@tracked lastTransition?: Transition;

beforeModel(transition: Transition) {
this.session.requireAuthentication(transition, 'login');
this.oidc.checkForOidcTokenAndRedirect();

this.lastTransition = transition;
}
Expand Down
17 changes: 16 additions & 1 deletion app/routes/login.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,18 @@
import Route from '@ember/routing/route';
import RouterService from '@ember/routing/router-service';
import { inject as service } from '@ember/service';

export default class LoginRoute extends Route {}
import ENV from 'irene/config/environment';

export default class LoginRoute extends Route {
@service declare session: any;
@service declare router: RouterService;

activate() {
if (this.session.isAuthenticated) {
this.router.transitionTo(
ENV['ember-simple-auth']['routeIfAlreadyAuthenticated']
);
}
}
}
17 changes: 17 additions & 0 deletions app/routes/oidc/authorize.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import Route from '@ember/routing/route';
import { inject as service } from '@ember/service';
import OidcService from 'irene/services/oidc';

export default class OidcAuthorizeRoute extends Route {
@service declare oidc: OidcService;

queryParams = {
oidc_token: {
refreshModel: true,
},
};

async model({ oidc_token }: { oidc_token: string }) {
return await this.oidc.fetchOidcAuthorizationDataOrRedirect(oidc_token);
}
}
9 changes: 9 additions & 0 deletions app/routes/oidc/error.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import Route from '@ember/routing/route';

import OidcErrorController, { OidcError } from 'irene/controllers/oidc/error';

export default class OidcErrorRoute extends Route {
setupController(controller: OidcErrorController, error: OidcError) {
controller.set('error', error);
}
}
17 changes: 17 additions & 0 deletions app/routes/oidc/redirect.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import Route from '@ember/routing/route';
import { inject as service } from '@ember/service';
import OidcService from 'irene/services/oidc';

export default class OidcRedirectRoute extends Route {
@service declare oidc: OidcService;

queryParams = {
oidc_token: {
refreshModel: true,
},
};

async model({ oidc_token }: { oidc_token: string }) {
await this.oidc.validateOidcTokenOrRedirect(oidc_token);
}
}
Loading

0 comments on commit 62e1ad8

Please sign in to comment.