@oada/lookup
Version:
JavaScript utility library to lookup OADA documents such as Well-Known (RFC 5785) resource, e.g., oada-configuration, openid-configuration, etc, and public OADA client registrations
177 lines (149 loc) • 4.58 kB
text/typescript
/**
* @license
* Copyright 2014-2022 Open Ag Data Alliance
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import url from 'url';
import request, { HTTPError } from 'superagent';
import { OADAError } from '@oada/error';
// Fetch a Well-Known (RFC 5785) Resource. The hostname will automatically be
// parsed from any URL.
export async function wellKnown(
hostname: string,
suffix: string,
{ timeout = 1000 } = {}
) {
// Parse hostname for just hostname parts
const parts = url.parse(hostname, false, true);
// Typical use: hostname = agcloud.com --> parts.path == "agcloud.com"...
if (!parts.hostname) {
parts.hostname = parts.path;
}
parts.protocol = 'https';
parts.pathname = `/.well-known/${suffix}`;
parts.path = null;
parts.search = null;
parts.query = null;
parts.hash = null;
const response = await request
.get(url.format(parts))
.accept('application/json')
.timeout(timeout);
// Make sure request was OK and that it was valid JSON (body was parsed)
if (response.ok && Object.keys(response.body).length > 0) {
return response.body as unknown;
}
if (response.ok) {
throw new Error(`Invalid ${suffix} Well-Known`);
}
const { error } = response as { error: HTTPError };
throw new OADAError(
error.text,
error.status,
`${suffix} at ${hostname} not found`,
error.path
);
}
export interface JWK {
kty: string;
}
export interface ClientRegistration {
clientId: string;
redirectURLs: string[];
licenses: Array<{ id: string; name: string }>;
keys: JWK[];
name: string;
contact: string;
puc: string;
}
// Fetch a client registration from an OADA client id.
export async function clientRegistration(
clientId: string,
{ timeout = 1000 } = {}
) {
// Parse client id
const cid = clientId.split('@');
if (cid.length !== 2) {
throw new Error('Invalid clientId');
}
const host = `https://${cid[1]}`;
// Get the OADA configuration of where the client is hosted
const config = (await wellKnown(host, 'oada-client-discovery', {
timeout,
})) as { client_discovery: string };
// Verify the host support client discovery
if (!config.client_discovery) {
throw new Error('Host does not support client discovery');
}
// Discover the clients registration
const response = await request
.get(config.client_discovery)
.query({ clientId })
.accept('application/json')
.timeout(timeout);
if (response.ok && Object.keys(response.body).length > 0) {
return response.body as ClientRegistration;
}
if (response.ok) {
throw new Error('Invalid client registration');
}
const { error } = response as { error: HTTPError };
throw new OADAError(error.text, error.status, 'Client not found', error.path);
}
// Fetch a JSON Web Key Set
export async function jwks(uri: string, { timeout = 1000 } = {}) {
// Get JWKs document
const response = await request
.get(uri)
.accept('application/json')
.timeout(timeout);
if (
response.ok &&
Object.keys(response.body).length > 0 &&
response.body.keys
) {
return response.body as { keys: JWK[] };
}
if (response.ok) {
throw new Error('Invalid JWKs document');
}
const { error } = response as { error: HTTPError };
throw new OADAError(
error.text,
error.status,
'Client public keys not found',
error.path
);
}
// Fetch trusted client discovery providers list
export async function trustedCDP({ timeout = 1000 } = {}) {
// Get trusted CDP list
const response = await request
.get('https://oada.github.io/oada-trusted-lists/client-discovery.json')
.accept('text/plain')
.timeout(timeout);
if (response.ok && response.body.length > 0) {
return response.body as string[];
}
if (response.ok) {
throw new Error('Invalid trusted client discovery provider list');
}
const { error } = response as { error: HTTPError };
throw new OADAError(
error.text,
error.status,
'Trusted client discovery provider list not found',
error.path
);
}