@solid/community-server
Version:
Community Solid Server: an open and modular implementation of the Solid specifications
110 lines • 5.18 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.ClientIdAdapterFactory = exports.ClientIdAdapter = void 0;
const cross_fetch_1 = require("cross-fetch");
const global_logger_factory_1 = require("global-logger-factory");
const ErrorUtil_1 = require("../../util/errors/ErrorUtil");
const FetchUtil_1 = require("../../util/FetchUtil");
const HeaderUtil_1 = require("../../util/HeaderUtil");
const Vocabularies_1 = require("../../util/Vocabularies");
const PassthroughAdapterFactory_1 = require("./PassthroughAdapterFactory");
/**
* This {@link Adapter} redirects the `find` call to its source adapter.
* In case no client data was found in the source for the given Client ID,
* this class will do an HTTP GET request to that Client ID.
* If the result is a valid Client ID document, that will be returned instead.
*
* See https://solidproject.org/TR/2022/oidc-20220328#clientids-document.
*/
class ClientIdAdapter extends PassthroughAdapterFactory_1.PassthroughAdapter {
logger = (0, global_logger_factory_1.getLoggerFor)(this);
converter;
constructor(name, source, converter) {
super(name, source);
this.converter = converter;
}
async find(id) {
let payload = await this.source.find(id);
// No payload is stored for the given Client ID.
// Try to see if valid client metadata is found at the given Client ID.
// The oidc-provider library will check if the redirect_uri matches an entry in the list of redirect_uris,
// so no extra checks are needed from our side.
if (!payload && this.name === 'Client' && (0, HeaderUtil_1.hasScheme)(id, 'http', 'https')) {
this.logger.debug(`Looking for payload data at ${id}`);
if (!/^https:|^http:\/\/localhost(?::\d+)?(?:\/|$)/u.test(id)) {
throw new Error(`SSL is required for client_id authentication unless working locally.`);
}
const response = await (0, cross_fetch_1.fetch)(id);
if (response.status !== 200) {
throw new Error(`Unable to access data at ${id}: ${await response.text()}`);
}
const data = await response.text();
let json;
try {
json = JSON.parse(data);
const contexts = Array.isArray(json['@context']) ? json['@context'] : [json['@context']];
// We can only parse as simple JSON if the @context is correct
if (!contexts.includes('https://www.w3.org/ns/solid/oidc-context.jsonld')) {
throw new Error('Missing context https://www.w3.org/ns/solid/oidc-context.jsonld');
}
}
catch (error) {
json = undefined;
this.logger.debug(`Found unexpected client ID for ${id}: ${(0, ErrorUtil_1.createErrorMessage)(error)}`);
}
if (json) {
// Need to make sure the document is about the id
if (json.client_id !== id) {
throw new Error('The client registration `client_id` field must match the client ID');
}
payload = json;
}
else {
// Since the client ID does not match the default JSON-LD we try to interpret it as RDF
payload = await this.parseRdfClientId(data, id, response);
}
// `token_endpoint_auth_method: 'none'` prevents oidc-provider from requiring a client_secret
// eslint-disable-next-line ts/naming-convention
payload = { ...payload, token_endpoint_auth_method: 'none' };
}
// Will also be returned if no valid client data was found above
return payload;
}
/**
* Parses RDF data found at a Client ID.
*
* @param data - Raw data from the Client ID.
* @param id - The actual Client ID.
* @param response - Response object from the request.
*/
async parseRdfClientId(data, id, response) {
const representation = await (0, FetchUtil_1.responseToDataset)(response, this.converter, data);
// Find the valid redirect URIs
const redirectUris = [];
for await (const entry of representation.data) {
const triple = entry;
if (triple.predicate.equals(Vocabularies_1.OIDC.terms.redirect_uris)) {
redirectUris.push(triple.object.value);
}
}
/* eslint-disable ts/naming-convention */
return {
client_id: id,
redirect_uris: redirectUris,
};
/* eslint-enable ts/naming-convention */
}
}
exports.ClientIdAdapter = ClientIdAdapter;
class ClientIdAdapterFactory extends PassthroughAdapterFactory_1.PassthroughAdapterFactory {
converter;
constructor(source, converter) {
super(source);
this.converter = converter;
}
createStorageAdapter(name) {
return new ClientIdAdapter(name, this.source.createStorageAdapter(name), this.converter);
}
}
exports.ClientIdAdapterFactory = ClientIdAdapterFactory;
//# sourceMappingURL=ClientIdAdapterFactory.js.map