Skip to content

Commit

Permalink
feat: handle callback from identity provider (#91)
Browse files Browse the repository at this point in the history
* feat: get session from redirect

* feat: set session when authenticating

* test: test get session in authenticate machine

* test: added tests for getSession
  • Loading branch information
wouteraj authored Apr 30, 2021
1 parent adf56cc commit 2b376a1
Show file tree
Hide file tree
Showing 16 changed files with 255 additions and 90 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ export class FormElementComponent<T> extends RxLitElement {
updated(changed: PropertyValues) {
super.updated(changed);

if(changed.has('actor')) {
if(changed.has('actor') && this.actor) {
// Subscribes to the field's validation results.
this.subscribe('validationResults', from(this.actor).pipe(
map((state) => state.context?.validation?.filter((result) => result.field === this.field)),
Expand Down
1 change: 1 addition & 0 deletions packages/nde-erfgoed-core/lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,5 @@ export * from './stores/resource';
export * from './stores/store';
export * from './solid/solid-mock.service';
export * from './solid/solid-sdk.service';
export * from './solid/solid-session';
export * from './solid/solid.service';
7 changes: 3 additions & 4 deletions packages/nde-erfgoed-core/lib/solid/solid-mock.service.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { ArgumentError } from '../errors/argument-error';
import { Logger } from '../logging/logger';
import { SolidSession } from './solid-session';
import { SolidService } from './solid.service';

/**
Expand Down Expand Up @@ -83,16 +84,14 @@ export class SolidMockService extends SolidService {
* Handles the post-login logic, as well as the restoration
* of sessions on page refreshes
*/
async handleIncomingRedirect(): Promise<unknown> {
async getSession(): Promise<SolidSession> {
this.logger.debug(SolidMockService.name, 'Trying to retrieve session');

if (!this.profiles) {
throw new ArgumentError('Argument this.profiles should be set.', this.profiles);
}

throw new Error();

return { isLoggedIn: true, webId: this.profiles[0].webId };
return null;
}

/**
Expand Down
39 changes: 24 additions & 15 deletions packages/nde-erfgoed-core/lib/solid/solid-sdk.service.spec.ts
Original file line number Diff line number Diff line change
@@ -1,27 +1,36 @@
// import * as solid from '@inrupt/solid-client-authn-browser';
// import { of } from 'rxjs';
// import { ConsoleLogger } from '../logging/console-logger';
// import { LoggerLevel } from '../logging/logger-level';
// import { SolidSdkService } from './solid-sdk.service';
// jest.mock('@inrupt/solid-client-authn-browser');
import { authn } from '@digita-ai/nde-erfgoed-client';
import { ConsoleLogger } from '../logging/console-logger';
import { LoggerLevel } from '../logging/logger-level';
import { SolidSDKService } from './solid-sdk.service';

jest.mock('@digita-ai/nde-erfgoed-client');
describe('SolidService', () => {
// let service: SolidService;
let service: SolidSDKService;

// beforeEach(async () => {
// const logger = new ConsoleLogger(LoggerLevel.silly, LoggerLevel.silly);
// service = new SolidSdkService(logger);
// });
beforeEach(async () => {
const logger = new ConsoleLogger(LoggerLevel.silly, LoggerLevel.silly);
service = new SolidSDKService(logger);
});

// afterEach(() => {
// // clear spies
// jest.clearAllMocks();
// });
// afterEach(() => {
// // clear spies
// jest.clearAllMocks();
// });

it('should be correctly instantiated', () => {
expect(true).toBeTruthy();
});

it.each([
[ {webId: 'lorem', isLoggedIn: true}, {webId: 'lorem'} ],
[ {webId: 'lorem', isLoggedIn: false}, null ],
[ null, null ],
])('should call handleIncomingRedirect when getting session', async (resolved, result) => {
authn.handleIncomingRedirect.mockResolvedValue(resolved);

expect(await service.getSession()).toEqual(result);
});

// describe('login()', () => {

// it.each([ null, undefined ])('should error when WebID is %s', async () => {
Expand Down
7 changes: 5 additions & 2 deletions packages/nde-erfgoed-core/lib/solid/solid-sdk.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { ArgumentError } from '../errors/argument-error';
import { Logger } from '../logging/logger';
import { NotImplementedError } from '../errors/not-implemented-error';
import { SolidService } from './solid.service';
import { SolidSession } from './solid-session';

/**
* An implementation of the Solid service which uses Solid Client.
Expand Down Expand Up @@ -46,10 +47,12 @@ export class SolidSDKService extends SolidService {
* Handles the post-login logic, as well as the restoration
* of sessions on page refreshes
*/
async handleIncomingRedirect(): Promise<unknown> {
async getSession(): Promise<SolidSession> {
this.logger.debug(SolidSDKService.name, 'Trying to retrieve session');

throw new NotImplementedError();
const session = await authn.handleIncomingRedirect();

return session && session.isLoggedIn ? { webId: session.webId } : null;
}

/**
Expand Down
9 changes: 9 additions & 0 deletions packages/nde-erfgoed-core/lib/solid/solid-session.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/**
* Represents a session with a Solid identity provider.
*/
export interface SolidSession {
/**
* The WebID of the authenticated user.
*/
webId: string;
}
4 changes: 3 additions & 1 deletion packages/nde-erfgoed-core/lib/solid/solid.service.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { SolidSession } from './solid-session';

/**
* Service for interacting with Solid pods
*/
Expand All @@ -23,7 +25,7 @@ export abstract class SolidService {
* Handles the post-login logic, as well as the restoration
* of sessions on page refreshes
*/
public abstract handleIncomingRedirect(): Promise<unknown>;
public abstract getSession(): Promise<SolidSession>;

/**
* Redirects the user to their OIDC provider
Expand Down
16 changes: 11 additions & 5 deletions packages/nde-erfgoed-manage/lib/app.events.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Alert, Event } from '@digita-ai/nde-erfgoed-components';
import { SolidSession } from '@digita-ai/nde-erfgoed-core';
import { assign, choose, send } from 'xstate/lib/actions';
import { AppContext } from './app.machine';

Expand Down Expand Up @@ -40,7 +41,12 @@ export interface LoggedOutEvent extends Event<AppEvents> { type: AppEvents.LOGGE
/**
* An event which is dispatched when an error occurs.
*/
export interface LoggedInEvent extends Event<AppEvents> { type: AppEvents.LOGGED_IN }
export interface LoggedInEvent extends Event<AppEvents> { type: AppEvents.LOGGED_IN; session: SolidSession }

/**
* Union type of app events.
*/
export type AppEvent = LoggedInEvent | LoggedOutEvent | ErrorEvent | DismissAlertEvent | AddAlertEvent;

/**
* Actions for the alerts component.
Expand All @@ -54,7 +60,7 @@ export const error = (err: Error | string) => send({ type: AppEvents.ERROR, data
/**
* Action which adds an alert to the machine's context, if it doesn't already exist.
*/
export const addAlert = choose<AppContext, Event<AppEvents>>([
export const addAlert = choose<AppContext, AddAlertEvent>([
{
cond: (context: AppContext, event: AddAlertEvent) => !event.alert,
actions: [
Expand All @@ -63,7 +69,7 @@ export const addAlert = choose<AppContext, Event<AppEvents>>([
},
{
actions: [
assign<AppContext, Event<AppEvents>>({
assign<AppContext, AddAlertEvent>({
alerts: (context: AppContext, event: AddAlertEvent) => [
...context.alerts ? context.alerts.filter((alert: Alert) => alert.message !== event.alert.message) : [],
event.alert,
Expand All @@ -76,7 +82,7 @@ export const addAlert = choose<AppContext, Event<AppEvents>>([
/**
* Action which dismisses an alert in the machine's context, if it doesn't already exist.
*/
export const dismissAlert = choose<AppContext, Event<AppEvents>>([
export const dismissAlert = choose<AppContext, DismissAlertEvent>([
{
cond: (context: AppContext, event: DismissAlertEvent) => (!event || !event.alert) ? true : false,
actions: [
Expand All @@ -85,7 +91,7 @@ export const dismissAlert = choose<AppContext, Event<AppEvents>>([
},
{
actions: [
assign<AppContext, Event<AppEvents>>({
assign<AppContext, DismissAlertEvent>({
alerts: (context: AppContext, event: DismissAlertEvent) => [
...context.alerts ? context.alerts.filter((alert) => alert.message !== event.alert.message) : [],
],
Expand Down
63 changes: 52 additions & 11 deletions packages/nde-erfgoed-manage/lib/app.machine.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Alert } from '@digita-ai/nde-erfgoed-components';
import { ConsoleLogger, LoggerLevel, SolidMockService } from '@digita-ai/nde-erfgoed-core';
import { interpret, Interpreter } from 'xstate';
import { AppEvents } from './app.events';
import { AppContext, appMachine } from './app.machine';
Expand All @@ -7,11 +8,14 @@ describe('AppMachine', () => {
let machine: Interpreter<AppContext>;

beforeEach(() => {
machine = interpret<AppContext>(appMachine.withContext({
alerts: [],
session: null,
loggedIn: false,
}));
machine = interpret<AppContext>(
appMachine(
new SolidMockService(new ConsoleLogger(LoggerLevel.silly, LoggerLevel.silly)),
)
.withContext({
alerts: [],
}),
);
});

it('should be correctly instantiated', () => {
Expand All @@ -30,7 +34,9 @@ describe('AppMachine', () => {

it('should not add duplicate alert to context when sending addAlert', () => {
const alert: Alert = {type: 'success', message: 'foo'};
machine = interpret<AppContext>(appMachine.withContext({
machine = interpret<AppContext>(appMachine(
new SolidMockService(new ConsoleLogger(LoggerLevel.silly, LoggerLevel.silly)),
).withContext({
alerts: [ alert ],
session: null,
loggedIn: false,
Expand All @@ -57,11 +63,14 @@ describe('AppMachine', () => {

it('should dismiss alert in context when sending dismissAlert', () => {
const alert: Alert = {type: 'success', message: 'foo'};
machine = interpret<AppContext>(appMachine.withContext({
alerts: [ alert ],
session: null,
loggedIn: false,
}));
machine = interpret<AppContext>(appMachine(
new SolidMockService(new ConsoleLogger(LoggerLevel.silly, LoggerLevel.silly)),
)
.withContext({
alerts: [ alert ],
session: null,
loggedIn: false,
}));
machine.start();
expect(machine.state.context.alerts.length).toBe(1);
machine.send(AppEvents.DISMISS_ALERT, { alert });
Expand Down Expand Up @@ -99,4 +108,36 @@ describe('AppMachine', () => {

machine.send(AppEvents.ERROR);
});

it('should assign session when logged in', async (done) => {
machine.onChange((context) => {
if(context.session?.webId === 'lorem') {
done();
}
});

machine.start();

machine.send({ type:AppEvents.LOGGED_IN, session: { webId: 'lorem' } });
});

it('should send logged in when authenticate machine is done', async (done) => {
const solid = new SolidMockService(new ConsoleLogger(LoggerLevel.silly, LoggerLevel.silly));
solid.getSession = jest.fn(async () => ({ webId: 'lorem' }));

machine = interpret<AppContext>(
appMachine(solid)
.withContext({
alerts: [],
}),
);

machine.onEvent((event) => {
if(event.type === AppEvents.LOGGED_IN && event.session?.webId === 'lorem') {
done();
}
});

machine.start();
});
});
Loading

0 comments on commit 2b376a1

Please sign in to comment.