Files
outline/server/utils/oauth.ts
Tom Moor 728790e38f feat: Validate Google, Azure, OIDC SSO access (#3590)
* chore: Store expiresAt on UserAuthentications. This represents the time that the accessToken is no longer valid and should be exchanged using the refreshToken

* feat: Check and expire Google SSO

* fix: Better handling of multiple auth methods
Added more docs

* fix: Retry access validation with network errors

* Small refactor, add Azure token validation support

* doc

* test

* lint

* OIDC refresh support

* CheckSSOAccessTask -> ValidateSSOAccessTask
Added lastValidatedAt column
Skip checks if validated within 5min
Some edge cases around encrypted columns
2022-06-05 13:18:51 -07:00

84 lines
1.9 KiB
TypeScript

import fetch from "fetch-with-proxy";
import { AuthenticationError, InvalidRequestError } from "../errors";
export default abstract class OAuthClient {
private clientId: string;
private clientSecret: string;
protected endpoints = {
authorize: "",
token: "",
userinfo: "",
};
constructor(clientId: string, clientSecret: string) {
this.clientId = clientId;
this.clientSecret = clientSecret;
}
userInfo = async (accessToken: string) => {
let data;
let response;
try {
response = await fetch(this.endpoints.userinfo, {
method: "GET",
headers: {
Authorization: `Bearer ${accessToken}`,
"Content-Type": "application/json",
},
});
data = await response.json();
} catch (err) {
throw InvalidRequestError(err.message);
}
const success = response.status >= 200 && response.status < 300;
if (!success) {
throw AuthenticationError();
}
return data;
};
rotateToken = async (
refreshToken: string
): Promise<{
accessToken: string;
refreshToken?: string;
expiresAt: Date;
}> => {
let data;
let response;
try {
response = await fetch(this.endpoints.token, {
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
},
body: new URLSearchParams({
client_id: this.clientId,
client_secret: this.clientSecret,
refresh_token: refreshToken,
grant_type: "refresh_token",
}),
});
data = await response.json();
} catch (err) {
throw InvalidRequestError(err.message);
}
const success = response.status >= 200 && response.status < 300;
if (!success) {
throw AuthenticationError();
}
return {
refreshToken: data.refresh_token,
accessToken: data.access_token,
expiresAt: new Date(Date.now() + data.expires_in * 1000),
};
};
}