mirror of
https://codeberg.org/emersion/gamja.git
synced 2024-11-14 19:05:01 -05:00
110 lines
3 KiB
JavaScript
110 lines
3 KiB
JavaScript
|
function formatQueryString(params) {
|
||
|
let l = [];
|
||
|
for (let k in params) {
|
||
|
l.push(encodeURIComponent(k) + "=" + encodeURIComponent(params[k]));
|
||
|
}
|
||
|
return l.join("&");
|
||
|
}
|
||
|
|
||
|
export async function fetchServerMetadata(url) {
|
||
|
// TODO: handle path in config.oauth2.url
|
||
|
let resp;
|
||
|
try {
|
||
|
resp = await fetch(url + "/.well-known/oauth-authorization-server");
|
||
|
if (!resp.ok) {
|
||
|
throw new Error(`HTTP error: ${resp.status} ${resp.statusText}`);
|
||
|
}
|
||
|
} catch (err) {
|
||
|
console.warn("OAuth 2.0 server doesn't support Authorization Server Metadata (retrying with OpenID Connect Discovery): ", err);
|
||
|
resp = await fetch(url + "/.well-known/openid-configuration");
|
||
|
if (!resp.ok) {
|
||
|
throw new Error(`HTTP error: ${resp.status} ${resp.statusText}`);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
let data = await resp.json();
|
||
|
if (!data.issuer) {
|
||
|
throw new Error("Missing issuer in response");
|
||
|
}
|
||
|
if (!data.authorization_endpoint) {
|
||
|
throw new Error("Missing authorization_endpoint in response");
|
||
|
}
|
||
|
if (!data.token_endpoint) {
|
||
|
throw new Error("Missing authorization_endpoint in response");
|
||
|
}
|
||
|
if (!data.response_types_supported.includes("code")) {
|
||
|
throw new Error("Server doesn't support authorization code response type");
|
||
|
}
|
||
|
return data;
|
||
|
}
|
||
|
|
||
|
export function redirectAuthorize({ serverMetadata, clientId, redirectUri, scope }) {
|
||
|
// TODO: move fragment to query string in redirect_uri
|
||
|
// TODO: use the state param to prevent cross-site request
|
||
|
// forgery
|
||
|
let params = {
|
||
|
response_type: "code",
|
||
|
client_id: clientId,
|
||
|
redirect_uri: redirectUri,
|
||
|
};
|
||
|
if (scope) {
|
||
|
params.scope = scope;
|
||
|
}
|
||
|
window.location.assign(serverMetadata.authorization_endpoint + "?" + formatQueryString(params));
|
||
|
}
|
||
|
|
||
|
function buildPostHeaders(clientId, clientSecret) {
|
||
|
let headers = {
|
||
|
"Content-Type": "application/x-www-form-urlencoded",
|
||
|
"Accept": "application/json",
|
||
|
};
|
||
|
if (clientSecret) {
|
||
|
headers["Authorization"] = "Basic " + btoa(encodeURIComponent(clientId) + ":" + encodeURIComponent(clientSecret));
|
||
|
}
|
||
|
return headers;
|
||
|
}
|
||
|
|
||
|
export async function exchangeCode({ serverMetadata, redirectUri, code, clientId, clientSecret }) {
|
||
|
let data = {
|
||
|
grant_type: "authorization_code",
|
||
|
code,
|
||
|
redirect_uri: redirectUri,
|
||
|
};
|
||
|
if (!clientSecret) {
|
||
|
data.client_id = clientId;
|
||
|
}
|
||
|
|
||
|
let resp = await fetch(serverMetadata.token_endpoint, {
|
||
|
method: "POST",
|
||
|
headers: buildPostHeaders(clientId, clientSecret),
|
||
|
body: formatQueryString(data),
|
||
|
});
|
||
|
|
||
|
if (!resp.ok) {
|
||
|
throw new Error(`HTTP error: ${resp.status} ${resp.statusText}`);
|
||
|
}
|
||
|
data = await resp.json();
|
||
|
|
||
|
if (data.error) {
|
||
|
throw new Error("Authentication failed: " + (data.error_description || data.error));
|
||
|
}
|
||
|
|
||
|
return data;
|
||
|
}
|
||
|
|
||
|
export async function introspectToken({ serverMetadata, token, clientId, clientSecret }) {
|
||
|
let resp = await fetch(serverMetadata.introspection_endpoint, {
|
||
|
method: "POST",
|
||
|
headers: buildPostHeaders(clientId, clientSecret),
|
||
|
body: formatQueryString({ token }),
|
||
|
});
|
||
|
if (!resp.ok) {
|
||
|
throw new Error(`HTTP error: ${resp.status} ${resp.statusText}`);
|
||
|
}
|
||
|
let data = await resp.json();
|
||
|
if (!data.active) {
|
||
|
throw new Error("Expired token");
|
||
|
}
|
||
|
return data;
|
||
|
}
|