mirror of
https://github.com/CommunitySolidServer/CommunitySolidServer.git
synced 2024-10-03 14:55:10 +00:00
feat: Full rework of account management
Complete rewrite of the account management and related systems. Makes the architecture more modular, allowing for easier extensions and configurations.
This commit is contained in:
43
src/http/input/metadata/AuthorizationParser.ts
Normal file
43
src/http/input/metadata/AuthorizationParser.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
import { DataFactory } from 'n3';
|
||||
import type { NamedNode } from 'rdf-js';
|
||||
import type { HttpRequest } from '../../../server/HttpRequest';
|
||||
import { matchesAuthorizationScheme } from '../../../util/HeaderUtil';
|
||||
import { SOLID_META } from '../../../util/Vocabularies';
|
||||
import type { RepresentationMetadata } from '../../representation/RepresentationMetadata';
|
||||
import { MetadataParser } from './MetadataParser';
|
||||
import namedNode = DataFactory.namedNode;
|
||||
|
||||
/**
|
||||
* Parses specific authorization schemes and stores their value as metadata.
|
||||
* The keys of the input `authMap` should be the schemes,
|
||||
* and the values the corresponding predicate that should be used to store the value in the metadata.
|
||||
* The scheme will be sliced off the value, after which it is used as the object in the metadata triple.
|
||||
*
|
||||
* This should be used for custom authorization schemes,
|
||||
* for things like OIDC tokens a {@link CredentialsExtractor} should be used.
|
||||
*/
|
||||
export class AuthorizationParser extends MetadataParser {
|
||||
private readonly authMap: Record<string, NamedNode>;
|
||||
|
||||
public constructor(authMap: Record<string, string>) {
|
||||
super();
|
||||
this.authMap = Object.fromEntries(
|
||||
Object.entries(authMap).map(([ scheme, uri ]): [string, NamedNode] => [ scheme, namedNode(uri) ]),
|
||||
);
|
||||
}
|
||||
|
||||
public async handle(input: { request: HttpRequest; metadata: RepresentationMetadata }): Promise<void> {
|
||||
const authHeader = input.request.headers.authorization;
|
||||
if (!authHeader) {
|
||||
return;
|
||||
}
|
||||
for (const [ scheme, uri ] of Object.entries(this.authMap)) {
|
||||
if (matchesAuthorizationScheme(scheme, authHeader)) {
|
||||
// This metadata should not be stored
|
||||
input.metadata.add(uri, authHeader.slice(scheme.length + 1), SOLID_META.ResponseMetadata);
|
||||
// There can only be 1 match
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
36
src/http/input/metadata/CookieParser.ts
Normal file
36
src/http/input/metadata/CookieParser.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
import { parse } from 'cookie';
|
||||
import { DataFactory } from 'n3';
|
||||
import type { NamedNode } from 'rdf-js';
|
||||
import type { HttpRequest } from '../../../server/HttpRequest';
|
||||
import { SOLID_META } from '../../../util/Vocabularies';
|
||||
import type { RepresentationMetadata } from '../../representation/RepresentationMetadata';
|
||||
import { MetadataParser } from './MetadataParser';
|
||||
import namedNode = DataFactory.namedNode;
|
||||
|
||||
/**
|
||||
* Parses the cookie header and stores their values as metadata.
|
||||
* The keys of the input `cookieMap` should be the cookie names,
|
||||
* and the values the corresponding predicate that should be used to store the value in the metadata.
|
||||
* The values of the cookies will be used as objects in the generated triples
|
||||
*/
|
||||
export class CookieParser extends MetadataParser {
|
||||
private readonly cookieMap: Record<string, NamedNode>;
|
||||
|
||||
public constructor(cookieMap: Record<string, string>) {
|
||||
super();
|
||||
this.cookieMap = Object.fromEntries(
|
||||
Object.entries(cookieMap).map(([ name, uri ]): [string, NamedNode] => [ name, namedNode(uri) ]),
|
||||
);
|
||||
}
|
||||
|
||||
public async handle(input: { request: HttpRequest; metadata: RepresentationMetadata }): Promise<void> {
|
||||
const cookies = parse(input.request.headers.cookie ?? '');
|
||||
for (const [ name, uri ] of Object.entries(this.cookieMap)) {
|
||||
const value = cookies[name];
|
||||
if (value) {
|
||||
// This metadata should not be stored
|
||||
input.metadata.add(uri, value, SOLID_META.ResponseMetadata);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
50
src/http/output/metadata/CookieMetadataWriter.ts
Normal file
50
src/http/output/metadata/CookieMetadataWriter.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
import { serialize } from 'cookie';
|
||||
import type { NamedNode } from 'n3';
|
||||
import { DataFactory } from 'n3';
|
||||
import type { HttpResponse } from '../../../server/HttpResponse';
|
||||
import { addHeader } from '../../../util/HeaderUtil';
|
||||
import type { RepresentationMetadata } from '../../representation/RepresentationMetadata';
|
||||
import { MetadataWriter } from './MetadataWriter';
|
||||
|
||||
/**
|
||||
* Generates the necessary `Set-Cookie` header if a cookie value is detected in the metadata.
|
||||
* The keys of the input `cookieMap` should be the URIs of the predicates
|
||||
* used in the metadata when the object is a cookie value.
|
||||
* The value of the map are objects that contain the name of the cookie,
|
||||
* and the URI that is used to store the expiration date in the metadata, if any.
|
||||
* If no expiration date is found in the metadata, none will be set for the cookie,
|
||||
* causing it to be a session cookie.
|
||||
*/
|
||||
export class CookieMetadataWriter extends MetadataWriter {
|
||||
private readonly cookieMap: Map<NamedNode, { name: string; expirationUri?: NamedNode }>;
|
||||
|
||||
public constructor(cookieMap: Record<string, { name: string; expirationUri?: string }>) {
|
||||
super();
|
||||
this.cookieMap = new Map<NamedNode, { name: string; expirationUri?: NamedNode }>(Object.entries(cookieMap)
|
||||
.map(([ uri, { name, expirationUri }]): [ NamedNode, { name: string; expirationUri?: NamedNode } ] =>
|
||||
[
|
||||
DataFactory.namedNode(uri),
|
||||
{
|
||||
name,
|
||||
expirationUri: expirationUri ? DataFactory.namedNode(expirationUri) : undefined,
|
||||
},
|
||||
]));
|
||||
}
|
||||
|
||||
public async handle(input: { response: HttpResponse; metadata: RepresentationMetadata }): Promise<void> {
|
||||
const { response, metadata } = input;
|
||||
for (const [ uri, { name, expirationUri }] of this.cookieMap.entries()) {
|
||||
const value = metadata.get(uri)?.value;
|
||||
if (value) {
|
||||
const expiration = expirationUri && metadata.get(expirationUri)?.value;
|
||||
const expires = typeof expiration === 'string' ? new Date(expiration) : undefined;
|
||||
// Not setting secure flag since not all tools realize those cookies are also valid for http://localhost.
|
||||
// Not setting the httpOnly flag as that would prevent JS API access.
|
||||
// SameSite: Lax makes it so the cookie gets sent if the origin is the server,
|
||||
// or if the browser navigates there from another site.
|
||||
// Setting the path to `/` so it applies to the entire server.
|
||||
addHeader(response, 'Set-Cookie', serialize(name, value, { path: '/', sameSite: 'lax', expires }));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user