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 12, 2024
1 parent 2b97d4d commit 8de8066
Show file tree
Hide file tree
Showing 13 changed files with 403 additions and 30 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;
71 changes: 71 additions & 0 deletions app/components/oidc-authorize/index.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
<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 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 as |li|>
<li.leftIcon @disabled={{true}}>
<AkIcon @iconName='chevron-right' />
</li.leftIcon>

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

<AkDivider />

<AkStack
class='p-3'
@alignItems='center'
@justifyContent='space-between'
@spacing='1.5'
>
<AkButton
class='w-full'
@variant='outlined'
@color='neutral'
@disabled={{this.authorizeOidc.isRunning}}
>
{{t 'cancel'}}
</AkButton>

<AkButton
class='w-full'
@loading={{this.authorizeOidc.isRunning}}
{{on 'click' (perform this.authorizeOidc)}}
>
{{t 'authorize'}}
</AkButton>
</AkStack>

<AkStack
@direction='column'
@alignItems='center'
@justifyContent='center'
@spacing='0.5'
>
<AkTypography @color='textSecondary' @variant='body3'>
{{t 'oidcModule.authorizeRedirectText'}}
</AkTypography>

<AkTypography @variant='body3'>
{{this.redirectUrl}}
</AkTypography>
</AkStack>
</div>
</div>
</div>
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);
}
}
67 changes: 67 additions & 0 deletions app/components/oidc-authorize/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import Component from '@glimmer/component';
import { inject as service } from '@ember/service';
import { task } from 'ember-concurrency';

import NetworkService from 'irene/services/network';
import { OidcAuthorizationResponse } from 'irene/routes/oidc/authorize';

interface OidcAuthorizeResult {
valid: boolean;
redirect_url: null | string;
error: null | {
code: string;
description: string;
};
}

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

export default class OidcAuthorizeComponent extends Component<OidcAuthorizeSignature> {
@service declare network: NetworkService;
@service('notifications') declare notify: NotificationService;
@service('browser/window') declare window: Window;

oidcAuthorizationEndpoint = '/api/v2/oidc/authorization/authorize';

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

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

get redirectUrl() {
return this.args.data?.validation_result?.redirect_url;
}

authorizeOidc = task(async () => {
const res = await this.network.post(this.oidcAuthorizationEndpoint, {
oidc_token: this.args.token,
allow: true,
});

const data = (await res.json()) as OidcAuthorizeResult;

if (data.error) {
this.notify.error(data.error.description);

return;
}

if (data.valid && data.redirect_url) {
this.window.location.href = data.redirect_url;
}
});
}

declare module '@glint/environment-ember-loose/registry' {
export default interface Registry {
OidcAuthorize: typeof OidcAuthorizeComponent;
}
}
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', function () {
this.route('redirect');
this.route('authorize');
});

this.route('register');

this.route('register-via-invite', {
Expand Down
22 changes: 21 additions & 1 deletion app/routes/authenticated.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { inject as service } from '@ember/service';
import { isEmpty } from '@ember/utils';
import Route from '@ember/routing/route';
import RouterService from '@ember/routing/router-service';
import { action } from '@ember/object';
import Transition from '@ember/routing/transition';
import { tracked } from '@glimmer/tracking';
Expand Down Expand Up @@ -29,15 +30,34 @@ export default class AuthenticatedRoute extends Route {
@service declare websocket: any;
@service declare integration: IntegrationService;
@service declare store: Store;
@service declare router: RouterService;
@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');
const isAuthenticated = this.session.requireAuthentication(
transition,
'login'
);

this.lastTransition = transition;

if (isAuthenticated) {
const oidc_token = this.window.sessionStorage.getItem('oidc_token');

if (oidc_token) {
this.router.transitionTo('oidc.redirect', {
queryParams: {
oidc_token,
},
});

this.window.sessionStorage.removeItem('oidc_token');
}
}
}

async model() {
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']
);
}
}
}
Loading

0 comments on commit 8de8066

Please sign in to comment.