Rewrite identity and access management

Signed-off-by: Bryan Frimin <bryan@getprobo.com>
This commit is contained in:
Bryan Frimin
2025-12-03 19:23:15 +01:00
parent 4ed3f5a067
commit 74fc3b8cd1
201 changed files with 32895 additions and 23649 deletions

View 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
View 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;
};
};

View File

@@ -0,0 +1,2 @@
export { makeFetchQuery } from "./fetch";
export * from "./errors";