Rewrite identity and access management
Signed-off-by: Bryan Frimin <bryan@getprobo.com>
This commit is contained in:
53
packages/relay/src/errors.ts
Normal file
53
packages/relay/src/errors.ts
Normal file
@@ -0,0 +1,53 @@
|
||||
export class UnAuthenticatedError extends Error {
|
||||
constructor(message?: string) {
|
||||
super(message || "UNAUTHENTICATED");
|
||||
this.name = "UnAuthenticatedError";
|
||||
Object.setPrototypeOf(this, UnAuthenticatedError.prototype);
|
||||
}
|
||||
}
|
||||
|
||||
export class InternalServerError extends Error {
|
||||
constructor() {
|
||||
super("INTERNAL_SERVER_ERROR");
|
||||
this.name = "InternalServerError";
|
||||
Object.setPrototypeOf(this, InternalServerError.prototype);
|
||||
}
|
||||
}
|
||||
|
||||
export class AuthenticationRequiredError extends Error {
|
||||
public redirectUrl: string;
|
||||
public requiresSaml: boolean;
|
||||
public organizationId: string;
|
||||
public samlConfigId?: string;
|
||||
|
||||
constructor(extensions: {
|
||||
redirectUrl: string;
|
||||
requiresSaml: boolean;
|
||||
organizationId: string;
|
||||
samlConfigId?: string;
|
||||
}) {
|
||||
super("AUTHENTICATION_REQUIRED");
|
||||
this.name = "AuthenticationRequiredError";
|
||||
Object.setPrototypeOf(this, AuthenticationRequiredError.prototype);
|
||||
this.redirectUrl = extensions.redirectUrl;
|
||||
this.requiresSaml = extensions.requiresSaml;
|
||||
this.organizationId = extensions.organizationId;
|
||||
this.samlConfigId = extensions.samlConfigId;
|
||||
}
|
||||
}
|
||||
|
||||
export class UnauthorizedError extends Error {
|
||||
constructor(message?: string) {
|
||||
super(message || "UNAUTHORIZED");
|
||||
this.name = "UnauthorizedError";
|
||||
Object.setPrototypeOf(this, UnauthorizedError.prototype);
|
||||
}
|
||||
}
|
||||
|
||||
export class ForbiddenError extends Error {
|
||||
constructor(message?: string) {
|
||||
super(message || "FORBIDDEN");
|
||||
this.name = "ForbiddenError";
|
||||
Object.setPrototypeOf(this, ForbiddenError.prototype);
|
||||
}
|
||||
}
|
||||
112
packages/relay/src/fetch.ts
Normal file
112
packages/relay/src/fetch.ts
Normal file
@@ -0,0 +1,112 @@
|
||||
import { type FetchFunction } from "relay-runtime";
|
||||
import {
|
||||
InternalServerError,
|
||||
UnAuthenticatedError,
|
||||
AuthenticationRequiredError,
|
||||
UnauthorizedError,
|
||||
ForbiddenError,
|
||||
} from "./errors";
|
||||
import { GraphQLError } from "graphql";
|
||||
|
||||
const hasUnauthenticatedError = (error: GraphQLError) =>
|
||||
error.extensions?.code == "UNAUTHENTICATED";
|
||||
|
||||
const hasAuthenticationRequiredError = (error: GraphQLError) =>
|
||||
error.extensions?.code == "AUTHENTICATION_REQUIRED";
|
||||
|
||||
const hasUnauthorizedError = (error: GraphQLError) =>
|
||||
error.extensions?.code == "UNAUTHORIZED";
|
||||
|
||||
const hasForbiddenError = (error: GraphQLError) =>
|
||||
error.extensions?.code == "FORBIDDEN";
|
||||
|
||||
export const makeFetchQuery = (endpoint: string): FetchFunction => {
|
||||
return async (request, variables, _, uploadables) => {
|
||||
const requestInit: RequestInit = {
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
headers: {},
|
||||
};
|
||||
|
||||
if (uploadables) {
|
||||
const formData = new FormData();
|
||||
formData.append(
|
||||
"operations",
|
||||
JSON.stringify({
|
||||
operationName: request.name,
|
||||
query: request.text,
|
||||
variables: variables,
|
||||
}),
|
||||
);
|
||||
|
||||
const uploadableMap: {
|
||||
[key: string]: string[];
|
||||
} = {};
|
||||
|
||||
Object.keys(uploadables).forEach((key, index) => {
|
||||
uploadableMap[index] = [`variables.${key}`];
|
||||
});
|
||||
|
||||
formData.append("map", JSON.stringify(uploadableMap));
|
||||
|
||||
Object.keys(uploadables).forEach((key, index) => {
|
||||
formData.append(index.toString(), uploadables[key]);
|
||||
});
|
||||
|
||||
requestInit.body = formData;
|
||||
} else {
|
||||
requestInit.headers = {
|
||||
Accept: "application/graphql-response+json; charset=utf-8, application/json; charset=utf-8",
|
||||
"Content-Type": "application/json",
|
||||
};
|
||||
|
||||
requestInit.body = JSON.stringify({
|
||||
operationName: request.name,
|
||||
query: request.text,
|
||||
variables,
|
||||
});
|
||||
}
|
||||
|
||||
const response = await fetch(endpoint, requestInit);
|
||||
|
||||
if (response.status === 500) {
|
||||
throw new InternalServerError();
|
||||
}
|
||||
|
||||
const json = await response.json();
|
||||
|
||||
if (json.errors) {
|
||||
const errors = json.errors as GraphQLError[];
|
||||
|
||||
const unauthenticatedError = errors.find(hasUnauthenticatedError);
|
||||
if (unauthenticatedError) {
|
||||
throw new UnAuthenticatedError(unauthenticatedError.message);
|
||||
}
|
||||
|
||||
const authRequiredError = errors.find(hasAuthenticationRequiredError);
|
||||
if (authRequiredError?.extensions) {
|
||||
const { redirectUrl, requiresSaml, organizationId, samlConfigId } =
|
||||
authRequiredError.extensions;
|
||||
|
||||
throw new AuthenticationRequiredError({
|
||||
redirectUrl: redirectUrl as string,
|
||||
requiresSaml: requiresSaml as boolean,
|
||||
organizationId: organizationId as string,
|
||||
samlConfigId: samlConfigId as string | undefined,
|
||||
});
|
||||
}
|
||||
|
||||
const unauthorizedError = errors.find(hasUnauthorizedError);
|
||||
if (unauthorizedError) {
|
||||
throw new UnauthorizedError(unauthorizedError.message);
|
||||
}
|
||||
|
||||
const forbiddenError = errors.find(hasForbiddenError);
|
||||
if (forbiddenError) {
|
||||
throw new ForbiddenError(forbiddenError.message);
|
||||
}
|
||||
}
|
||||
|
||||
return json;
|
||||
};
|
||||
};
|
||||
2
packages/relay/src/index.ts
Normal file
2
packages/relay/src/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export { makeFetchQuery } from "./fetch";
|
||||
export * from "./errors";
|
||||
Reference in New Issue
Block a user