diff --git a/web/app/controllers/authenticate.ts b/web/app/controllers/authenticate.ts index f5e56bc28..f1ac42cc1 100644 --- a/web/app/controllers/authenticate.ts +++ b/web/app/controllers/authenticate.ts @@ -1,14 +1,10 @@ import Controller from "@ember/controller"; import { inject as service } from "@ember/service"; import { action } from "@ember/object"; -import RouterService from "@ember/routing/router-service"; -import Transition from "@ember/routing/transition"; +import SessionService from "hermes/services/session"; export default class AuthenticateController extends Controller { - @service declare router: RouterService; - @service declare session: any; - - previousTransition: Transition | null = null; + @service declare session: SessionService; protected get currentYear(): number { return new Date().getFullYear(); @@ -16,19 +12,6 @@ export default class AuthenticateController extends Controller { @action protected authenticate(): void { this.session.authenticate("authenticator:torii", "google-oauth2-bearer"); - - // Capture the previousTransition locally if it exists - let _previousTransition = this.previousTransition; - - if (_previousTransition) { - // Clear the previousTransition class property - this.previousTransition = null; - - // Retry the initial transition - _previousTransition.retry(); - } else { - this.router.transitionTo("authenticated.dashboard"); - } } } diff --git a/web/app/routes/authenticate.ts b/web/app/routes/authenticate.ts index 2e7531056..283146e70 100644 --- a/web/app/routes/authenticate.ts +++ b/web/app/routes/authenticate.ts @@ -1,8 +1,9 @@ import Route from "@ember/routing/route"; import { inject as service } from "@ember/service"; +import SessionService from "hermes/services/session"; export default class AuthenticateRoute extends Route { - @service declare session: any; + @service declare session: SessionService; beforeModel() { this.session.prohibitAuthentication("/"); diff --git a/web/app/routes/authenticated.ts b/web/app/routes/authenticated.ts index 59e11a5e4..4e04d3b2c 100644 --- a/web/app/routes/authenticated.ts +++ b/web/app/routes/authenticated.ts @@ -1,34 +1,38 @@ import Route from "@ember/routing/route"; import { inject as service } from "@ember/service"; -import ConfigService from "hermes/services/config"; -import AuthenticateController from "hermes/controllers/authenticate"; import AuthenticatedUserService from "hermes/services/authenticated-user"; -import Transition from "@ember/routing/transition"; +import window from "ember-window-mock"; +import SessionService from "hermes/services/session"; export default class AuthenticatedRoute extends Route { - @service declare session: any; + @service declare session: SessionService; @service declare authenticatedUser: AuthenticatedUserService; - @service("config") declare configSvc: ConfigService; async afterModel(): Promise { - // Load user info await this.authenticatedUser.loadInfo.perform(); } - async beforeModel(transition: Transition): Promise { - // Check if the request requires authentication and if so, preserve the URL + async beforeModel(transition: any): Promise { + // If the user isn't authenticated, transition to the auth screen let requireAuthentication = this.session.requireAuthentication( transition, "authenticate" ); - if (!requireAuthentication && transition.to.name != "authenticated.index") { - let authenticateController = this.controllerFor( - "authenticate" - ) as AuthenticateController; + let target = window.sessionStorage.getItem( + this.session.SESSION_STORAGE_KEY + ); + if ( + !target && + !requireAuthentication && + transition.to.name != "authenticated" + ) { + // ember-simple-auth uses this value to set cookies when fastboot is enabled: https://github.com/mainmatter/ember-simple-auth/blob/a7e583cf4d04d6ebc96b198a8fa6dde7445abf0e/packages/ember-simple-auth/addon/-internals/routing.js#L12 - // Set previous transition to preserve URL - authenticateController.previousTransition = transition; + window.sessionStorage.setItem( + this.session.SESSION_STORAGE_KEY, + transition.intent.url + ); } } } diff --git a/web/app/services/algolia.ts b/web/app/services/algolia.ts index c043c8354..966b5cc6e 100644 --- a/web/app/services/algolia.ts +++ b/web/app/services/algolia.ts @@ -15,6 +15,7 @@ import { assert } from "@ember/debug"; import ConfigService from "./config"; import { FacetOption, FacetRecord, FacetRecords } from "hermes/types/facets"; import FetchService from "./fetch"; +import SessionService from "./session"; export const HITS_PER_PAGE = 12; export const MAX_VALUES_PER_FACET = 100; @@ -26,9 +27,9 @@ export type AlgoliaFacetsObject = NonNullable; export default class AlgoliaService extends Service { @service("config") declare configSvc: ConfigService; @service("fetch") declare fetchSvc: FetchService; + @service declare session: SessionService; @service declare authenticatedUser: AuthenticatedUserService; - // TODO: use actual type. - @service session: any; + /** * A shorthand getter for the authenticatedUser's email. diff --git a/web/app/services/authenticated-user.ts b/web/app/services/authenticated-user.ts index ea540d35d..149082012 100644 --- a/web/app/services/authenticated-user.ts +++ b/web/app/services/authenticated-user.ts @@ -5,6 +5,7 @@ import Store from "@ember-data/store"; import { assert } from "@ember/debug"; import { task } from "ember-concurrency"; import FetchService from "hermes/services/fetch"; +import SessionService from "./session"; export interface AuthenticatedUser { email: string; @@ -25,8 +26,8 @@ enum SubscriptionType { export default class AuthenticatedUserService extends Service { @service("fetch") declare fetchSvc: FetchService; + @service declare session: SessionService; @service declare store: Store; - @service declare session: any; @tracked subscriptions: Subscription[] | null = null; @tracked private _info: AuthenticatedUser | null = null; diff --git a/web/app/services/session.ts b/web/app/services/session.ts new file mode 100644 index 000000000..58d9247b8 --- /dev/null +++ b/web/app/services/session.ts @@ -0,0 +1,28 @@ +import { inject as service } from "@ember/service"; +import RouterService from "@ember/routing/router-service"; +import EmberSimpleAuthSessionService from "ember-simple-auth/services/session"; +import window from "ember-window-mock"; + +export default class SessionService extends EmberSimpleAuthSessionService { + @service declare router: RouterService; + + readonly SESSION_STORAGE_KEY: string = "hermes.redirectTarget"; + + // ember-simple-auth only uses a cookie to track redirect target if you're using fastboot, otherwise it keeps track of the redirect target as a parameter on the session service. See the source here: https://github.com/mainmatter/ember-simple-auth/blob/a7e583cf4d04d6ebc96b198a8fa6dde7445abf0e/packages/ember-simple-auth/addon/-internals/routing.js#L33-L50 + // + // Because we redirect as part of the authentication flow, the parameter storing the transition gets reset. Instead, we keep track of the redirectTarget in browser sessionStorage and override the handleAuthentication method as recommended by ember-simple-auth. + + handleAuthentication(routeAfterAuthentication: string) { + let redirectTarget = window.sessionStorage.getItem(this.SESSION_STORAGE_KEY); + let transition; + + if (redirectTarget) { + transition = this.router.transitionTo(redirectTarget); + } else { + transition = this.router.transitionTo(routeAfterAuthentication); + } + transition.followRedirects().then(() => { + window.sessionStorage.removeItem(this.SESSION_STORAGE_KEY); + }); + } +} diff --git a/web/types/ember-simple-auth/services/session.d.ts b/web/types/ember-simple-auth/services/session.d.ts new file mode 100644 index 000000000..c45ce6384 --- /dev/null +++ b/web/types/ember-simple-auth/services/session.d.ts @@ -0,0 +1,25 @@ +// Reference: https://github.com/mainmatter/ember-simple-auth/blob/master/packages/ember-simple-auth/addon/services/session.js + +import Service from "@ember/service"; +import Evented from "@ember/object/evented"; +import Transition from "@ember/routing/transition"; + +export interface Data { + authenticated: { + access_token: string; + }; +} + +declare module "ember-simple-auth/services/session" { + export default class EmberSimpleAuthSessionService extends Service.extend(Evented) { + data: Data; + setup: () => void; + authenticate(...args: any[]): RSVP.Promise; + invalidate(...args: any): RSVP.Promise; + requireAuthentication( + transition: Transition, + routeOrCallback: string | function + ): RSVP.Promise; + prohibitAuthentication(routeOrCallback: string | function): RSVP.Promise; + } +}