Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Create authenticate feature #31

Merged
merged 32 commits into from
Apr 29, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
dfecf59
docs: created technical docs for authenticate setup
lem-onade Apr 15, 2021
cc38339
docs: added docs
lem-onade Apr 16, 2021
78cd372
Merge remote-tracking branch 'origin/develop' into docs/set-up-authen…
lem-onade Apr 19, 2021
bfe1890
docs: wrote machine, context, actions, .. docs
lem-onade Apr 21, 2021
90c39cc
docs: added state chart diagram
lem-onade Apr 22, 2021
31211d1
docs: typo authorize -> authenticate
lem-onade Apr 22, 2021
59676c1
docs: final changes
lem-onade Apr 22, 2021
b704fd6
docs: minor changes
lem-onade Apr 22, 2021
c4ab5a0
docs: minor changes
lem-onade Apr 22, 2021
862e980
docs: final changes
lem-onade Apr 22, 2021
54c685b
Merge branch 'develop' into docs/set-up-authenticate-feature-675145293
lem-onade Apr 23, 2021
5404101
docs: included feedback
lem-onade Apr 23, 2021
30bc627
docs: added solidservice docs
lem-onade Apr 23, 2021
2971f43
Merge branch 'docs/set-up-authenticate-feature-675145293' of github.c…
lem-onade Apr 23, 2021
aead78b
docs: sessioninfo edits
lem-onade Apr 26, 2021
417d6e5
Merge branch 'develop' into docs/set-up-authenticate-feature-675145293
lem-onade Apr 26, 2021
22d05d1
feat: created initial files for authenticate
lem-onade Apr 26, 2021
c3a5c56
chore: trying to add authenticate as parallel state node
lem-onade Apr 26, 2021
e01d3bf
Merge remote-tracking branch 'origin/develop' into docs/set-up-authen…
lem-onade Apr 27, 2021
9694658
feat: integrated authenticate machine into app machine
lem-onade Apr 27, 2021
ff19721
feat: basic implementation of authenticate feature
lem-onade Apr 27, 2021
6da6efd
chore: applied old feedback
lem-onade Apr 27, 2021
e0293c5
test: fixed tests
lem-onade Apr 27, 2021
0a95cb6
chore: minor changes
lem-onade Apr 27, 2021
d06daea
chore: cleaned up file structure
lem-onade Apr 27, 2021
1114859
test: fixed broken import in tests
lem-onade Apr 27, 2021
cb26b4e
chore: cleaned up authenticate feature
lem-onade Apr 28, 2021
e4ec4cf
chore: renamed login and logout events
lem-onade Apr 28, 2021
2365f39
fix: made sure logout is called
lem-onade Apr 28, 2021
0392c18
refactor: type improvements
wouteraj Apr 28, 2021
3435ce6
refactor: improve matching parallel states
wouteraj Apr 29, 2021
feebd30
refactor: no more any
wouteraj Apr 29, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -164,4 +164,4 @@ jobs:
location: 'west europe'
ports: 80
command-line: ./node_modules/.bin/community-solid-server -b https://pods.nde.digita.ai -p 80 --rootFilePath /tmp/css -c config/config-file.json -l debug
# ip-address: 'Private'
# ip-address: 'Private'
woutermont marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ describe('AlertComponent', () => {

beforeEach(() => {
component = window.document.createElement('nde-alert') as AlertComponent;
(component as any).createRenderRoot = () => this;
});

afterEach(() => {
Expand Down
21 changes: 11 additions & 10 deletions packages/nde-erfgoed-components/lib/demo/demo-form.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@ import { ArgumentError, Collection, MemoryTranslator, Translator } from '@digita
import { interpret, Interpreter, StateValueMap } from 'xstate';
import { RxLitElement } from 'rx-lit';
import { from } from 'rxjs';
import { map } from 'rxjs/operators';
import { map, tap } from 'rxjs/operators';
import { Login, Search } from '@digita-ai/nde-erfgoed-theme';
import { unsafeSVG } from 'lit-html/directives/unsafe-svg';
import { FormCleanlinessStates, FormContext, formMachine, FormRootStates, FormSubmissionStates, FormValidationStates } from '../forms/form.machine';
import { Event } from '../state/event';
import { FormValidatorResult } from '../forms/form-validator-result';
import { Schema } from '../state/schema';
import { FormEvents } from '../forms/form.events';
import { FormValidator } from '../forms/form-validator';

/**
* Validates the form and returns its results.
Expand All @@ -19,7 +19,7 @@ import { FormEvents } from '../forms/form.events';
* @param event The event which triggered the validation.
* @returns Results of the validation.
*/
export const validator = (context: FormContext<Collection>, event: Event<FormEvents>): FormValidatorResult[] => [
export const validator: FormValidator<Collection> = (context: FormContext<Collection>, event: Event<FormEvents>): FormValidatorResult[] => [
...context.data && context.data.name ? [] : [ { field: 'name', message: 'demo-form.name.required' } ],
...context.data && context.data.uri ? [] : [ { field: 'uri', message: 'demo-form.uri.required' } ],
];
Expand Down Expand Up @@ -50,7 +50,7 @@ export class DemoFormComponent extends RxLitElement {
* The actor controlling this component.
*/
@property({type: Object})
public actor: Interpreter<FormContext<Collection>, Schema<FormContext<Collection>, FormEvents>, Event<FormEvents>>;
public actor: Interpreter<FormContext<Collection>>;

/**
* Enables or disables the submit button.
Expand All @@ -65,8 +65,8 @@ export class DemoFormComponent extends RxLitElement {
constructor() {
super();

this.actor = interpret<FormContext<Collection>, any, Event<FormEvents>>(
formMachine(validator).withContext({
this.actor = interpret(
formMachine<Collection>(validator).withContext({
data: { uri: '', name: 'Test' },
original: { uri: '', name: 'Test' },
}),
Expand All @@ -87,10 +87,11 @@ export class DemoFormComponent extends RxLitElement {
}

this.subscribe('enableSubmit', from(this.actor).pipe(
map((state) => state.value as StateValueMap),
map((value) => value[FormRootStates.CLEANLINESS] === FormCleanlinessStates.DIRTY
&& value[FormRootStates.VALIDATION] === FormValidationStates.VALID
&& value[FormRootStates.SUBMISSION] === FormSubmissionStates.NOT_SUBMITTED),
map((state) => state.matches({
[FormRootStates.CLEANLINESS]: FormCleanlinessStates.DIRTY,
[FormRootStates.VALIDATION]: FormValidationStates.VALID,
[FormRootStates.SUBMISSION]: FormSubmissionStates.NOT_SUBMITTED,
})),
));
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,19 +1,18 @@
import { Collection } from '@digita-ai/nde-erfgoed-core';
import { interpret, Interpreter } from 'xstate';
import { Event } from '../state/event';
import { Schema } from '../state/schema';
import { FormElementComponent } from './form-element.component';
import { FormValidatorResult } from './form-validator-result';
import { FormEvents } from './form.events';
import { FormContext, formMachine } from './form.machine';

describe('FormElementComponent', () => {
let component: FormElementComponent;
let machine: Interpreter<FormContext<Collection>, Schema<FormContext<Collection>, FormEvents>, Event<FormEvents>>;
let component: FormElementComponent<Collection>;
let machine: Interpreter<FormContext<Collection>>;

beforeEach(() => {
machine = interpret<FormContext<Collection>, any, Event<FormEvents>>(
formMachine(
machine = interpret(
formMachine<Collection>(
(context: FormContext<Collection>, event: Event<FormEvents>): FormValidatorResult[] => [
...context.data && context.data.name ? [] : [ { field: 'name', message: 'demo-form.name.required' } ],
...context.data && context.data.uri ? [] : [ { field: 'uri', message: 'demo-form.uri.required' } ],
Expand All @@ -25,8 +24,7 @@ describe('FormElementComponent', () => {
validation: [],
}),
);
component = window.document.createElement('nde-form-element') as FormElementComponent;
(component as any).createRenderRoot = () => this;
component = window.document.createElement('nde-form-element') as FormElementComponent<Collection>;

component.actor = machine;
component.field = 'name';
Expand Down
23 changes: 13 additions & 10 deletions packages/nde-erfgoed-components/lib/forms/form-element.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { FormEvents, FormUpdatedEvent } from './form.events';
/**
* A component which shows the details of a single collection.
*/
export class FormElementComponent extends RxLitElement {
export class FormElementComponent<T> extends RxLitElement {

/**
* The component's translator.
Expand All @@ -24,7 +24,7 @@ export class FormElementComponent extends RxLitElement {
* The name of the data attribute edited by the form element.
*/
@property({type: String})
public field: string;
public field: keyof T;

/**
* The element's form validation results.
Expand All @@ -36,13 +36,13 @@ export class FormElementComponent extends RxLitElement {
* The element's data.
*/
@internalProperty()
public data: any;
public data: T;

/**
* The actor controlling this component.
*/
@property({type: Object})
public actor: SpawnedActorRef<Event<FormEvents>, State<FormContext<any>>>;
public actor: SpawnedActorRef<Event<FormEvents>, State<FormContext<T>>>;

/**
* The styles associated with the component.
Expand Down Expand Up @@ -120,7 +120,7 @@ export class FormElementComponent extends RxLitElement {
*
* @param slotchangeEvent Event fired when slot is changed.
*/
handleInputSlotchange(slotchangeEvent: any) {
handleInputSlotchange(slotchangeEvent: UIEvent) {
if (!slotchangeEvent || !slotchangeEvent.target) {
throw new ArgumentError('Argument slotchangeEvent should be set.', slotchangeEvent);
}
Expand All @@ -137,17 +137,20 @@ export class FormElementComponent extends RxLitElement {
throw new ArgumentError('Argument this.actor should be set.', this.actor);
}

const childNodes: NodeListOf<HTMLElement> = slotchangeEvent.target.assignedNodes({flatten: true});
if(!typeof slotchangeEvent.target || !(slotchangeEvent.target instanceof HTMLSlotElement)) {
throw new ArgumentError('Argument slotchangeEvent.target should be set with a HTMLSlotElement.', this.actor);
}

const childNodes: Node[] = slotchangeEvent.target.assignedNodes({flatten: true});

childNodes.forEach((node) => {
if(node && node instanceof HTMLInputElement) {
const input = node as HTMLInputElement;

// Set the input field's default value.
input.value = this.data[this.field];
const fieldData = this.data[this.field];
node.value = typeof fieldData === 'string' ? fieldData : '';

// Send event when input field's value changes.
input.addEventListener('input', () => this.actor.send({type: FormEvents.FORM_UPDATED, value: input.value, field: this.field} as FormUpdatedEvent));
node.addEventListener('input', () => this.actor.send({type: FormEvents.FORM_UPDATED, value: node.value, field: this.field} as FormUpdatedEvent));
}
});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,4 @@ import { FormContext } from './form.machine';
/**
* Validates the form and returns validator results.
*/
export type FormValidator = (context: FormContext<any>, event: Event<FormEvents>) => FormValidatorResult[];
export type FormValidator<TData> = (context: FormContext<TData>, event: Event<FormEvents>) => FormValidatorResult[];
8 changes: 4 additions & 4 deletions packages/nde-erfgoed-components/lib/forms/form.events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,13 @@ export interface FormSubmittedEvent extends Event<FormEvents> { type: FormEvents
/**
* Updates the data in context.
*/
export const update = assign<FormContext<any>, FormUpdatedEvent>({
data: (context: FormContext<any>, event: FormUpdatedEvent) => ({...context.data ? context.data : {}, [event.field]: event.value}),
export const update = assign<FormContext<unknown>, FormUpdatedEvent>({
data: (context: FormContext<unknown>, event: FormUpdatedEvent) => (typeof context.data === 'object' ? {...context.data ? context.data : {}, [event.field]: event.value} : event.value),
});

/**
* Validates the data in context.
*/
export const validate = (validator: FormValidator) => assign<FormContext<any>, Event<FormEvents>>({
validation: (context: FormContext<any>, event: Event<FormEvents>) => [ ...validator(context, event) ],
export const validate = (validator: FormValidator<unknown>) => assign<FormContext<unknown>, Event<FormEvents>>({
validation: (context: FormContext<unknown>, event: Event<FormEvents>) => [ ...validator(context, event) ],
});
Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@
import { Collection } from '@digita-ai/nde-erfgoed-core';
import { interpret, Interpreter, StateValueMap } from 'xstate';
import { Event } from '../state/event';
import { Schema } from '../state/schema';
import { FormValidatorResult } from './form-validator-result';
import { FormEvents } from './form.events';
import { FormCleanlinessStates, FormContext, formMachine, FormRootStates, FormSubmissionStates, FormValidationStates } from './form.machine';

describe('FormMachine', () => {
let machine: Interpreter<FormContext<Collection>, Schema<FormContext<Collection>, FormEvents>, Event<FormEvents>>;
let machine: Interpreter<FormContext<Collection>>;

beforeEach(() => {
machine = interpret<FormContext<Collection>, any, Event<FormEvents>>(
machine = interpret<FormContext<Collection>>(
formMachine(
(context: FormContext<Collection>, event: Event<FormEvents>): FormValidatorResult[] => [
...context.data && context.data.name ? [] : [ { field: 'name', message: 'demo-form.name.required' } ],
Expand Down
8 changes: 4 additions & 4 deletions packages/nde-erfgoed-components/lib/forms/form.machine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ export type FormStates = FormRootStates | FormSubmissionStates | FormCleanliness
/**
* The form component machine.
*/
export const formMachine = (validator: FormValidator) => createMachine<FormContext<any>, Event<FormEvents>, State<FormStates, FormContext<any>>>({
export const formMachine = <T>(validator: FormValidator<T>) => createMachine<FormContext<T>, Event<FormEvents>, State<FormStates, FormContext<T>>>({
id: 'form',
type: 'parallel',
states: {
Expand Down Expand Up @@ -87,7 +87,7 @@ export const formMachine = (validator: FormValidator) => createMachine<FormConte
always: [
{
target: FormCleanlinessStates.PRISTINE,
cond: (context, event) => JSON.stringify(context.data) === JSON.stringify(context.original),
cond: (context: FormContext<T>) => JSON.stringify(context.data) === JSON.stringify(context.original),
},
{
target: FormCleanlinessStates.DIRTY,
Expand Down Expand Up @@ -132,7 +132,7 @@ export const formMachine = (validator: FormValidator) => createMachine<FormConte
always: [
{
target: FormSubmissionStates.SUBMITTED,
cond: (context, event) => context.validation === null || context.validation.length === 0,
cond: (context: FormContext<T>) => context.validation === null || context.validation.length === 0,
},
{
target: FormSubmissionStates.NOT_SUBMITTED,
Expand Down Expand Up @@ -180,7 +180,7 @@ export const formMachine = (validator: FormValidator) => createMachine<FormConte
always: [
{
target: FormValidationStates.VALID,
cond: (context, event) => context.validation === null || context.validation.length === 0,
cond: (context: FormContext<T>) => context.validation === null || context.validation.length === 0,
},
{
target: FormValidationStates.INVALID,
Expand Down
6 changes: 4 additions & 2 deletions packages/nde-erfgoed-components/lib/state/state.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import { StateValueMap, Typestate } from 'xstate';

/**
* Represents a state node with its corresponding context.
*/
export interface State<TStates, TContext> {
export interface State<TStates extends string, TContext> extends Typestate<TContext> {
/**
* The value of the state node.
*/
value: TStates;
value: TStates | StateValueMap;

/**
* The state's corresponding context.
Expand Down
2 changes: 2 additions & 0 deletions packages/nde-erfgoed-core/lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,5 @@ export * from './logging/logger';
export * from './stores/memory-store';
export * from './stores/resource';
export * from './stores/store';
export * from './solid/solid.service';
export * from './solid/solid-mock.service';
2 changes: 1 addition & 1 deletion packages/nde-erfgoed-core/lib/logging/console-logger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export class ConsoleLogger extends Logger {
* @param message Message that should be logged
* @param data Any relevant data that should be logged
*/
log(level: LoggerLevel, typeName: string, message: string, data?: any) {
log(level: LoggerLevel, typeName: string, message: string, data?: unknown) {
if (level === null || level === undefined) {
throw new ArgumentError('level should be set', typeName);
}
Expand Down
10 changes: 5 additions & 5 deletions packages/nde-erfgoed-core/lib/logging/logger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export abstract class Logger {
* @param message Message that should be logged
* @param data Any relevant data that should be logged
*/
info(typeName: string, message: string, data?: any) {
info(typeName: string, message: string, data?: unknown) {
if (!typeName) {
throw new ArgumentError('Typename should be set', typeName);
}
Expand All @@ -45,7 +45,7 @@ export abstract class Logger {
* @param message Message that should be logged
* @param data Any relevant data that should be logged
*/
debug(typeName: string, message: string, data?: any) {
debug(typeName: string, message: string, data?: unknown) {
if (!typeName) {
throw new ArgumentError('Typename should be set', typeName);
}
Expand All @@ -64,7 +64,7 @@ export abstract class Logger {
* @param message Message that should be logged
* @param data Any relevant data that should be logged
*/
warn(typeName: string, message: string, data?: any) {
warn(typeName: string, message: string, data?: unknown) {
if (!typeName) {
throw new ArgumentError('Typename should be set', typeName);
}
Expand All @@ -84,7 +84,7 @@ export abstract class Logger {
* @param error The error that was thrown
* @param caught The error that was caught
*/
error(typeName: string, message: string, error?: Error | any, caught?: any) {
error(typeName: string, message: string, error?: Error | unknown, caught?: unknown) {
if (!typeName) {
throw new ArgumentError('Typename should be set', typeName);
}
Expand All @@ -104,6 +104,6 @@ export abstract class Logger {
* @param message Message that should be logged
* @param data Any relevant data that should be logged
*/
abstract log(level: LoggerLevel, typeName: string, message: string, data?: any): void;
abstract log(level: LoggerLevel, typeName: string, message: string, data?: unknown): void;

}
65 changes: 65 additions & 0 deletions packages/nde-erfgoed-core/lib/solid/solid-mock.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { Observable, of } from 'rxjs';
import { Logger } from '../logging/logger';
import { SolidService } from './solid.service';

/**
* A mock implementation of the Solid service.
*/
export class SolidMockService extends SolidService {
woutermont marked this conversation as resolved.
Show resolved Hide resolved

/**
* Instantiates a solid mock service.
*
* @param logger The logger used in the service.
*/
constructor(private logger: Logger) {
super();
}

/**
* Makes sure the profile document of a WebID contains
* the necessary triples for authentication, and whether they are correct
*
* @param webId The WebID to validate
*/
validateWebId(webId: string): Observable<boolean> {
this.logger.debug(SolidMockService.name, 'Validating WebID', webId);
return of(true);
}

/**
* Retrieves the value of the oidcIssuer triple from a profile document
* for a given WebID
*
* @param webId The WebID for which to retrieve the OIDC issuer
*/
getIssuer(webId: string): Observable<string> {
this.logger.debug(SolidMockService.name, 'Retrieving issuer', webId);
return of('issuer');
}

/**
* Handles the post-login logic, as well as the restoration
* of sessions on page refreshes
*/
handleIncomingRedirect(): Observable<unknown> {
this.logger.debug(SolidMockService.name, 'Trying to retrieve session');
return of({ isLoggedIn: true });
}

/**
* Redirects the user to their OIDC provider
*/
login(): Observable<unknown> {
this.logger.debug(SolidMockService.name, 'Loggin in user');
return of(true);
}

/**
* Deauthenticates the user from their OIDC issuer
*/
logout(): Observable<unknown> {
this.logger.debug(SolidMockService.name, 'Logging out user');
return of(true);
}
}
Loading