Skip to content

Commit

Permalink
Merge pull request #2883 from mainmatter/migrate-authenticators-types…
Browse files Browse the repository at this point in the history
…cript

refactor(typescript): migrate BaseAuthenticator and OAuthPasswordGrant
  • Loading branch information
BobrImperator authored Dec 27, 2024
2 parents 290fb30 + b639ad2 commit 8fd4fff
Show file tree
Hide file tree
Showing 9 changed files with 210 additions and 75 deletions.
3 changes: 2 additions & 1 deletion packages/ember-simple-auth/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,8 @@
"prettier": "3.3.3",
"rollup": "4.25.0",
"rollup-plugin-copy": "3.5.0",
"typescript": "^5.7.2"
"typescript": "^5.7.2",
"typescript-event-target": "^1.1.1"
},
"publishConfig": {
"registry": "https://registry.npmjs.org"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
import EmberObject from '@ember/object';
import { TypedEventTarget, type TypedEventListener } from 'typescript-event-target';

class AuthenticatorEventTarget extends EventTarget {}
export interface AuthenticatorEvents {
sessionDataUpdated: CustomEvent<any>;
sessionDataInvalidated: CustomEvent;
}

class AuthenticatorEventTarget extends TypedEventTarget<AuthenticatorEvents> {}

/**
The base class for all authenticators. __This serves as a starting point for
Expand Down Expand Up @@ -109,7 +115,7 @@ export default class EsaBaseAuthenticator extends EmberObject {
@member
@public
*/
restore() {
restore(...args: any[]): Promise<unknown> {
return Promise.reject();
}

Expand Down Expand Up @@ -138,7 +144,7 @@ export default class EsaBaseAuthenticator extends EmberObject {
@member
@public
*/
authenticate() {
authenticate(...args: any[]): Promise<unknown> {
return Promise.reject();
}

Expand All @@ -163,26 +169,35 @@ export default class EsaBaseAuthenticator extends EmberObject {
@member
@public
*/
invalidate() {
invalidate(...args: any[]): Promise<unknown> {
return Promise.resolve();
}

on(event, cb) {
on<Event extends keyof AuthenticatorEvents>(
event: Event,
cb: TypedEventListener<AuthenticatorEvents, Event>
) {
this.authenticatorEvents.addEventListener(event, cb);
}

off(event, cb) {
off<Event extends keyof AuthenticatorEvents>(
event: Event,
cb: TypedEventListener<AuthenticatorEvents, Event>
) {
this.authenticatorEvents.removeEventListener(event, cb);
}

trigger(event, value) {
trigger<Event extends keyof AuthenticatorEvents>(
event: Event,
value: AuthenticatorEvents[Event]['detail']
) {
let customEvent;
if (value) {
customEvent = new CustomEvent(event, { detail: value });
} else {
customEvent = new CustomEvent(event);
}

this.authenticatorEvents.dispatchEvent(customEvent);
this.authenticatorEvents.dispatchTypedEvent(event, customEvent);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import { waitFor } from '@ember/test-waiters';

const JSON_CONTENT_TYPE = 'application/json';

export type NestedRecord = Record<string, string | Record<string, string>>;

/**
Authenticator that works with the Ruby gem
[devise](https://github.com/plataformatec/devise).
Expand Down Expand Up @@ -79,7 +81,7 @@ export default class DeviseAuthenticator extends BaseAuthenticator {
@return {Promise} A promise that when it resolves results in the session becoming or remaining authenticated
@public
*/
restore(data) {
restore(data: Record<string, NestedRecord>) {
return this._validate(data) ? Promise.resolve(data) : Promise.reject();
}

Expand All @@ -102,14 +104,14 @@ export default class DeviseAuthenticator extends BaseAuthenticator {
@return {Promise} A promise that when it resolves results in the session becoming authenticated. If authentication fails, the promise will reject with the server response; however, the authenticator reads that response already so if you need to read it again you need to clone the response object first
@public
*/
authenticate(identification, password) {
authenticate(identification: string, password: string) {
return new Promise((resolve, reject) => {
const { resourceName, identificationAttributeName, tokenAttributeName } = this.getProperties(
'resourceName',
'identificationAttributeName',
'tokenAttributeName'
);
const data = {};
let data: NestedRecord = {};
data[resourceName] = { password };
data[resourceName][identificationAttributeName] = identification;

Expand Down Expand Up @@ -161,7 +163,7 @@ export default class DeviseAuthenticator extends BaseAuthenticator {
@protected
*/
@waitFor
makeRequest(data, options = {}) {
makeRequest(data: NestedRecord, options: { url?: string } = {}) {
let url = options.url || this.get('serverTokenEndpoint');
let requestOptions = {};
let body = JSON.stringify(data);
Expand All @@ -178,7 +180,7 @@ export default class DeviseAuthenticator extends BaseAuthenticator {
return fetch(url, requestOptions);
}

_validate(data) {
_validate(data: Record<string, NestedRecord>) {
const tokenAttributeName = this.get('tokenAttributeName');
const identificationAttributeName = this.get('identificationAttributeName');
const resourceName = this.get('resourceName');
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
/** @module ember-simple-auth/authenticators/oauth2-implicit-grant **/

import { isEmpty } from '@ember/utils';
import BaseAuthenticator from './base';
/**
Parses the location hash (as received from `window.location.hash`) into an
Expand All @@ -15,20 +14,33 @@ import BaseAuthenticator from './base';
@return {Object} An obect with individual properties and values for the data parsed from the location hash
@memberof module:ember-simple-auth/authenticators/oauth2-implicit-grant
*/
export function parseResponse(locationHash) {
let params = {};
export function parseResponse(locationHash: string): Record<string, string> {
let params: Record<string, string> = {};
const query = locationHash.substring(locationHash.indexOf('?'));
const regex = /([^#?&=]+)=([^&]*)/g;
let match;

// decode all parameter pairs
while ((match = regex.exec(query)) !== null) {
params[decodeURIComponent(match[1])] = decodeURIComponent(match[2]);
const [_, key, value] = match;
if (key && value) {
params[decodeURIComponent(key)] = decodeURIComponent(value);
}
}

return params;
}

export type ImplicitGrantData = {
response_type: string;
client_id: string;
redirect_uri: string;
scope: string;
state: string;
access_token: string;
error?: string;
};

/**
Authenticator that conforms to OAuth 2
([RFC 6749](http://tools.ietf.org/html/rfc6749)), specifically the _"Implicit
Expand All @@ -54,7 +66,7 @@ export default class OAuth2ImplicitGrantAuthenticator extends BaseAuthenticator
@return {Promise} A promise that when it resolves results in the session becoming or remaining authenticated
@public
*/
restore(data) {
restore(data: ImplicitGrantData) {
return new Promise((resolve, reject) => {
if (!this._validateData(data)) {
return reject('Could not restore session - "access_token" missing.');
Expand All @@ -79,7 +91,7 @@ export default class OAuth2ImplicitGrantAuthenticator extends BaseAuthenticator
@return {Promise} A promise that when it resolves results in the session becoming authenticated
@public
*/
authenticate(hash) {
authenticate(hash: ImplicitGrantData) {
return new Promise((resolve, reject) => {
if (hash.error) {
reject(hash.error);
Expand All @@ -103,9 +115,8 @@ export default class OAuth2ImplicitGrantAuthenticator extends BaseAuthenticator
return Promise.resolve();
}

_validateData(data) {
_validateData(data: ImplicitGrantData) {
// see https://tools.ietf.org/html/rfc6749#section-4.2.2

return !isEmpty(data) && !isEmpty(data.access_token);
return data && data.access_token;
}
}
Loading

0 comments on commit 8fd4fff

Please sign in to comment.