feat: Allow switching accounts

* feat: Allow logging out on the consent page

* feat: log in with a different account cleanup

Co-authored-by: Joachim Van Herwegen <joachimvh@gmail.com>
This commit is contained in:
jaxoncreed 2022-08-02 23:19:22 -07:00 committed by GitHub
parent 9dcba1a288
commit 3fea5c98f5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 56 additions and 6 deletions

View File

@ -8,6 +8,7 @@
- A new FileSystemResourceLocker has been added. It allows for true threadsafe locking without external dependencies.
- The CSS can now run multithreaded with multiple workers, this is done with the `--workers` or `-w` flag.
- When starting the server through code, it is now possible to provide CLI value bindings as well in `AppRunner`.
- The user can choose to "Log in with a different account" on the consent page
### Data migration
The following actions are required if you are upgrading from a v4 server and want to retain your data.

View File

@ -56,13 +56,22 @@ export class ConsentHandler extends BaseInteractionHandler {
.map((key): [ string, unknown ] => [ key, metadata[key] ]),
);
jsonLd['@context'] = 'https://www.w3.org/ns/solid/oidc-context.jsonld';
const json = { client: jsonLd };
const json = { webId: oidcInteraction.session?.accountId, client: jsonLd };
return new BasicRepresentation(JSON.stringify(json), operation.target, APPLICATION_JSON);
}
protected async handlePost({ operation, oidcInteraction }: InteractionHandlerInput): Promise<never> {
const { remember } = await readJsonStream(operation.body.data);
const { remember, logOut } = await readJsonStream(operation.body.data);
if (logOut) {
const provider = await this.providerFactory.getProvider();
const session = (await provider.Session.find(oidcInteraction!.session!.cookie))!;
delete session.accountId;
await session.save(session.exp - Math.floor(Date.now() / 1000));
throw new FoundHttpError(oidcInteraction!.returnTo);
}
const grant = await this.getGrant(oidcInteraction!);
this.updateGrant(grant, oidcInteraction!.prompt.details, remember);

View File

@ -1,4 +1,5 @@
<h1>Authorize</h1>
<p id="webId">Your WebID: </p>
<p>The following client wants to do authorized requests in your name:</p>
<ul id="clientInfo">
</ul>
@ -13,10 +14,33 @@
</ol>
</fieldset>
<p class="actions"><button autofocus type="submit" name="submit">Consent</button></p>
<p class="actions">
<button autofocus type="submit" name="submit">Consent</button>
<button onclick="logOut(event)">Log in with a different account</button>
</p>
</form>
<script>
async function logOut(e) {
e.preventDefault();
const res = await fetch('', {
method: 'POST',
headers: { accept: 'application/json', 'content-type': 'application/json' },
body: JSON.stringify({ logOut: true }),
});
const json = await res.json();
location.href = json.location;
}
</script>
<script>
function addWebId(webId) {
const p = document.getElementById('webId');
const strong = document.createElement('strong')
strong.appendChild(document.createTextNode(webId));
p.appendChild(strong);
}
const clientInfo = document.getElementById('clientInfo');
function addClientInfo(text, value) {
if (value) {
@ -31,8 +55,9 @@
// Update the client information
(async() => {
const res = await fetch('', { headers: { accept: 'application/json' } })
const { client } = await res.json();
const res = await fetch('', { headers: { accept: 'application/json' } });
const { webId, client } = await res.json();
addWebId(webId);
addClientInfo('Name', client.client_name);
addClientInfo('ID', client.client_id);
})()

View File

@ -59,7 +59,10 @@ describe('A ConsentHandler', (): void => {
beforeEach(async(): Promise<void> => {
oidcInteraction = {
session: { accountId },
session: {
accountId,
save: jest.fn(),
},
// eslint-disable-next-line @typescript-eslint/naming-convention
params: { client_id: clientId },
prompt: { details: {}},
@ -76,6 +79,9 @@ describe('A ConsentHandler', (): void => {
Client: {
find: (id: string): any => (id ? { metadata: jest.fn().mockReturnValue(clientMetadata) } : undefined),
},
Session: {
find: (): Interaction['session'] => oidcInteraction.session,
},
/* eslint-enable @typescript-eslint/naming-convention */
} as any;
@ -106,6 +112,7 @@ describe('A ConsentHandler', (): void => {
...clientMetadata,
'@context': 'https://www.w3.org/ns/solid/oidc-context.jsonld',
},
webId: accountId,
});
});
@ -117,6 +124,7 @@ describe('A ConsentHandler', (): void => {
client: {
'@context': 'https://www.w3.org/ns/solid/oidc-context.jsonld',
},
webId: accountId,
});
});
@ -170,4 +178,11 @@ describe('A ConsentHandler', (): void => {
expect(grantFn.mock.results).toHaveLength(1);
expect(grantFn.mock.results[0].value.rejectedScopes).toEqual([ 'offline_access' ]);
});
it('deletes the accountId when logout is provided.', async(): Promise<void> => {
const operation = createPostJsonOperation({ logOut: true });
await expect(handler.handle({ operation, oidcInteraction })).rejects.toThrow(FoundHttpError);
expect((oidcInteraction!.session! as any).save).toHaveBeenCalledTimes(1);
expect(oidcInteraction!.session!.accountId).toBeUndefined();
});
});