feat: Add controls to IDP response JSON

Controls are now used in templates to prevent IDP URL hardcoding
This commit is contained in:
Joachim Van Herwegen
2021-08-25 14:49:57 +02:00
parent d68854a474
commit 32a182dde8
13 changed files with 80 additions and 58 deletions

View File

@@ -145,9 +145,8 @@ describe('A Solid server with IDP', (): void => {
it('initializes the session and logs in.', async(): Promise<void> => {
const url = await state.startSession();
const { login } = await state.parseLoginPage(url);
expect(typeof login).toBe('string');
await state.login(login, email, password);
await state.parseLoginPage(url);
await state.login(url, email, password);
expect(state.session.info?.webId).toBe(webId);
});
@@ -168,10 +167,10 @@ describe('A Solid server with IDP', (): void => {
it('can log in again.', async(): Promise<void> => {
const url = await state.startSession();
const form = await state.extractFormUrl(url);
expect(form.url.endsWith('/confirm')).toBe(true);
let res = await state.fetchIdp(url);
expect(res.status).toBe(200);
const res = await state.fetchIdp(form.url, 'POST', '', APPLICATION_X_WWW_FORM_URLENCODED);
res = await state.fetchIdp(url, 'POST', '', APPLICATION_X_WWW_FORM_URLENCODED);
const nextUrl = res.headers.get('location');
expect(typeof nextUrl).toBe('string');
@@ -226,16 +225,12 @@ describe('A Solid server with IDP', (): void => {
state = new IdentityTestState(baseUrl, redirectUrl, oidcIssuer);
});
it('initializes the session.', async(): Promise<void> => {
const url = await state.startSession();
const { login } = await state.parseLoginPage(url);
expect(typeof login).toBe('string');
nextUrl = login;
});
it('can not log in with the old password anymore.', async(): Promise<void> => {
const url = await state.startSession();
nextUrl = url;
await state.parseLoginPage(url);
const formData = stringify({ email, password });
const res = await state.fetchIdp(nextUrl, 'POST', formData, APPLICATION_X_WWW_FORM_URLENCODED);
const res = await state.fetchIdp(url, 'POST', formData, APPLICATION_X_WWW_FORM_URLENCODED);
expect(res.status).toBe(200);
expect(await res.text()).toContain('Incorrect password');
});
@@ -307,9 +302,8 @@ describe('A Solid server with IDP', (): void => {
it('initializes the session and logs in.', async(): Promise<void> => {
state = new IdentityTestState(baseUrl, redirectUrl, oidcIssuer);
const url = await state.startSession();
const { login } = await state.parseLoginPage(url);
expect(typeof login).toBe('string');
await state.login(login, newMail, password);
await state.parseLoginPage(url);
await state.login(url, newMail, password);
expect(state.session.info?.webId).toBe(newWebId);
});

View File

@@ -94,15 +94,14 @@ export class IdentityTestState {
return nextUrl;
}
public async parseLoginPage(url: string): Promise<{ register: string; login: string; forgotPassword: string }> {
public async parseLoginPage(url: string): Promise<{ register: string; forgotPassword: string }> {
const res = await this.fetchIdp(url);
expect(res.status).toBe(200);
const text = await res.text();
const register = this.extractUrl(text, 'a:contains("Sign up")', 'href');
const login = this.extractUrl(text, 'form', 'action');
const forgotPassword = this.extractUrl(text, 'a:contains("Forgot password")', 'href');
return { register, login, forgotPassword };
return { register, forgotPassword };
}
/**
@@ -118,21 +117,6 @@ export class IdentityTestState {
return this.handleLoginRedirect(nextUrl);
}
/**
* Calls the given URL and extracts the action URL from a form contained within the resulting body.
* Also returns the resulting body in case further parsing is needed.
*/
public async extractFormUrl(url: string): Promise<{ url: string; body: string }> {
const res = await this.fetchIdp(url);
expect(res.status).toBe(200);
const text = await res.text();
const formUrl = this.extractUrl(text, 'form', 'action');
return {
url: new URL(formUrl, this.baseUrl).href,
body: text,
};
}
/**
* Handles the redirect that happens after logging in.
*/

View File

@@ -30,7 +30,7 @@ import { readableToString } from '../../../src/util/StreamUtil';
import { CONTENT_TYPE, SOLID_HTTP, SOLID_META } from '../../../src/util/Vocabularies';
describe('An IdentityProviderHttpHandler', (): void => {
const apiVersion = '0.1';
const apiVersion = '0.2';
const baseUrl = 'http://test.com/';
const idpPath = '/idp';
let request: HttpRequest;
@@ -38,6 +38,7 @@ describe('An IdentityProviderHttpHandler', (): void => {
let requestParser: jest.Mocked<RequestParser>;
let providerFactory: jest.Mocked<ProviderFactory>;
let routes: { response: InteractionRoute; complete: InteractionRoute };
let controls: Record<string, string>;
let interactionCompleter: jest.Mocked<InteractionCompleter>;
let converter: jest.Mocked<RepresentationConverter>;
let errorHandler: jest.Mocked<ErrorHandler>;
@@ -74,16 +75,18 @@ describe('An IdentityProviderHttpHandler', (): void => {
];
routes = {
response: new InteractionRoute('/routeResponse',
response: new InteractionRoute('^/routeResponse$',
{ 'text/html': '/view1' },
handlers[0],
'login',
{ 'text/html': '/response1' }),
complete: new InteractionRoute('/routeComplete',
{ 'text/html': '/response1' },
{ response: '/routeResponse' }),
complete: new InteractionRoute('^/routeComplete$',
{ 'text/html': '/view2' },
handlers[1],
'other'),
};
controls = { response: 'http://test.com/idp/routeResponse' };
converter = {
handleSafe: jest.fn((input: RepresentationConverterArgs): Representation => {
@@ -129,7 +132,7 @@ describe('An IdentityProviderHttpHandler', (): void => {
const { response: mockResponse, result } = responseWriter.handleSafe.mock.calls[0][0];
expect(mockResponse).toBe(response);
expect(JSON.parse(await readableToString(result.data!)))
.toEqual({ apiVersion, errorMessage: '', prefilled: {}, authenticating: false });
.toEqual({ apiVersion, errorMessage: '', prefilled: {}, authenticating: false, controls });
expect(result.statusCode).toBe(200);
expect(result.metadata?.contentType).toBe('text/html');
expect(result.metadata?.get(SOLID_META.template)?.value).toBe(routes.response.viewTemplates['text/html']);
@@ -147,7 +150,8 @@ describe('An IdentityProviderHttpHandler', (): void => {
expect(responseWriter.handleSafe).toHaveBeenCalledTimes(1);
const { response: mockResponse, result } = responseWriter.handleSafe.mock.calls[0][0];
expect(mockResponse).toBe(response);
expect(JSON.parse(await readableToString(result.data!))).toEqual({ apiVersion, key: 'val', authenticating: false });
expect(JSON.parse(await readableToString(result.data!)))
.toEqual({ apiVersion, key: 'val', authenticating: false, controls });
expect(result.statusCode).toBe(200);
expect(result.metadata?.contentType).toBe('text/html');
expect(result.metadata?.get(SOLID_META.template)?.value).toBe(routes.response.responseTemplates['text/html']);
@@ -163,7 +167,7 @@ describe('An IdentityProviderHttpHandler', (): void => {
expect(responseWriter.handleSafe).toHaveBeenCalledTimes(1);
const { result } = responseWriter.handleSafe.mock.calls[0][0];
expect(JSON.parse(await readableToString(result.data!))).toEqual({ apiVersion, authenticating: true });
expect(JSON.parse(await readableToString(result.data!))).toEqual({ apiVersion, authenticating: true, controls });
});
it('errors for InteractionCompleteResults if no oidcInteraction is defined.', async(): Promise<void> => {
@@ -231,8 +235,9 @@ describe('An IdentityProviderHttpHandler', (): void => {
expect(responseWriter.handleSafe).toHaveBeenCalledTimes(1);
const { response: mockResponse, result } = responseWriter.handleSafe.mock.calls[0][0];
expect(mockResponse).toBe(response);
expect(JSON.parse(await readableToString(result.data!)))
.toEqual({ apiVersion, errorMessage: 'handle error', prefilled: { name: 'name' }, authenticating: false });
expect(JSON.parse(await readableToString(result.data!))).toEqual(
{ apiVersion, errorMessage: 'handle error', prefilled: { name: 'name' }, authenticating: false, controls },
);
expect(result.statusCode).toBe(200);
expect(result.metadata?.contentType).toBe('text/html');
expect(result.metadata?.get(SOLID_META.template)?.value).toBe(routes.response.viewTemplates['text/html']);
@@ -248,7 +253,7 @@ describe('An IdentityProviderHttpHandler', (): void => {
const { response: mockResponse, result } = responseWriter.handleSafe.mock.calls[0][0];
expect(mockResponse).toBe(response);
expect(JSON.parse(await readableToString(result.data!)))
.toEqual({ apiVersion, errorMessage: 'handle error', prefilled: {}, authenticating: false });
.toEqual({ apiVersion, errorMessage: 'handle error', prefilled: {}, authenticating: false, controls });
});
it('calls the errorHandler if there is a problem resolving the request.', async(): Promise<void> => {