Skip to content

Commit

Permalink
Merge pull request #303 from leolurch/feature/session
Browse files Browse the repository at this point in the history
Feature: Add supertokens sessions
  • Loading branch information
samuelstroschein authored Jan 23, 2023
2 parents 1cb1e6b + 852d76a commit ffab344
Show file tree
Hide file tree
Showing 20 changed files with 1,683 additions and 196 deletions.
955 changes: 868 additions & 87 deletions package-lock.json

Large diffs are not rendered by default.

18 changes: 17 additions & 1 deletion source-code/website/env.example
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,28 @@
# ------------------------------------


# Exactly 32 characters long string which is base64 encoded.
# Example: `S2V5LU11c3QtQmUtMTYtb3ItMzItb3ItNjQtYnllZWU=`
JWE_SECRET_KEY="<secret key>"

# 50a45f4482cd28494437a0f128cd0b6158cf6176
GITHUB_APP_CLIENT_SECRET="<secret>"

# url and port of of @inlang/cors-proxy
VITE_GIT_REQUEST_PROXY_PATH="/git-proxy/"

# https://docs.github.com/en/developers/apps/building-oauth-apps/authorizing-oauth-apps
VITE_GITHUB_APP_CLIENT_ID="<client id>"
VITE_GITHUB_APP_CLIENT_ID="<client id>"


# If you want to work on the session logic uncomment the following variables
# Sign up for a free dev instance here: https://supertokens.com/docs/session/introduction
# SUPERTOKENS_CONNECTION_URI=<INSERT_SUPERTOKENS_BACKEND_SERVICE_URI>
# SUPERTOKENS_API_KEY=<INSERT_SUPERTOKENS_BACKEND_SERVICE_API_KEY>

# Probably http://localhost:3000 or http://127.0.0.1 or https://inlang.com
# Required for supertokens to work
# VITE_FRONTEND_BASE_URL=<INSERT_FRONTEND_BASE_URL>

# Set to 'true' if you want to enable the supertokens session logic in the dev environment
# VITE_SUPERTOKENS_IN_DEV=true
89 changes: 72 additions & 17 deletions source-code/website/env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,16 @@ export type ServerSideEnv = ClientSideEnv & {
GOOGLE_TRANSLATE_API_KEY?: string;

/**
* The secret for signing cookies.
* The connection uri for super tokens.
* Only required in production or when using super tokens in dev by setting VITE_SUPERTOKENS_IN_DEV to 'true'.
*/
COOKIE_SECRET: string;
SUPERTOKENS_CONNECTION_URI?: string;

/**
* The API key for super tokens.
* Only required in production or when using super tokens in dev by setting VITE_SUPERTOKENS_IN_DEV to 'true'.
*/
SUPERTOKENS_API_KEY?: string;
};

/**
Expand All @@ -53,6 +60,11 @@ export type ServerSideEnv = ClientSideEnv & {
* for more information.
*/
export type ClientSideEnv = {
/**
* The environment mode.
*/
NODE_ENV: "development" | "production" | "test";

/**
* The url of the proxy server for git requests.
*/
Expand All @@ -70,6 +82,26 @@ export type ClientSideEnv = {
* Only available in production.
*/
VITE_SENTRY_DSN_CLIENT?: string;

/**
* The name of the super tokens app.
* Defaults to `inlang`.
*/
VITE_SUPERTOKENS_APP_NAME?: string;

/**
* Set to `true` to enable super tokens in your dev environment.
*
* Only affecting the dev.
*/
VITE_SUPERTOKENS_IN_DEV?: string;

/**
* The bas url of the frontend
* @example http://localhost:3000
* @example https://inlang.com
*/
VITE_FRONTEND_BASE_URL?: string;
};

/**
Expand Down Expand Up @@ -109,32 +141,55 @@ export async function serverSideEnv(): Promise<ServerSideEnv> {
}
}

/**
* Validate a single env variable.
* @param name The name of the env variable.
* @param value The value of the env variable.
* @throws Error if the env variable is invalid.
*/
const validateSingle = (name: string, value?: string) => {
if (value === undefined) {
throw Error(`Missing env variable ${name}`);
}
};

const validateSupertokens = (env: ServerSideEnv) => {
validateSingle("SUPERTOKENS_API_KEY", env.SUPERTOKENS_API_KEY);
validateSingle("SUPERTOKENS_CONNECTION_URI", env.SUPERTOKENS_CONNECTION_URI);
validateSingle("VITE_FRONTEND_BASE_URL", env.VITE_FRONTEND_BASE_URL);
};

/**
* Call this function as soon as possible to validate the env.
*
* Will throw an error if the env is invalid.
*/
export async function validateEnv() {
const env = await serverSideEnv();
// VITE_GIT_REQUEST_PROXY_PATH
if (env.VITE_GIT_REQUEST_PROXY_PATH === undefined) {
throw Error("Missing env variable VITE_CORS_PROXY_URL");
} else if (

validateSingle(
"VITE_GIT_REQUEST_PROXY_PATH",
env.VITE_GIT_REQUEST_PROXY_PATH
);
validateSingle("VITE_GITHUB_APP_CLIENT_ID", env.VITE_GITHUB_APP_CLIENT_ID);
validateSingle("JWE_SECRET_KEY", env.JWE_SECRET_KEY);
validateSingle("GITHUB_APP_CLIENT_SECRET", env.GITHUB_APP_CLIENT_SECRET);

if (env.NODE_ENV == "production") {
validateSupertokens(env);
} else {
if (env.VITE_SUPERTOKENS_IN_DEV !== undefined) {
validateSupertokens(env);
}
}

// in depth validation
if (
env.VITE_GIT_REQUEST_PROXY_PATH.startsWith("/") === false ||
env.VITE_GIT_REQUEST_PROXY_PATH.endsWith("/") === false
) {
throw Error(
"VITE_CORS_PROXY_URL must be a local path like that starts and ends with a slash `/` like `/git-proxy/`."
"VITE_GIT_REQUEST_PROXY_PATH must be a local path like that starts and ends with a slash `/` like `/git-proxy/`."
);
}
//
else if (env.VITE_GITHUB_APP_CLIENT_ID === undefined) {
throw Error("Missing env variable VITE_GITHUB_APP_CLIENT_ID");
} else if (env.JWE_SECRET_KEY === undefined) {
throw Error("Missing env variable JWE_SECRET_KEY");
} else if (env.GITHUB_APP_CLIENT_SECRET === undefined) {
throw Error("Missing env variable GITHUB_APP_CLIENT_SECRET");
} else if (env.COOKIE_SECRET === undefined) {
throw Error("Missing env variable COOKIE_SECRET");
}
}
6 changes: 6 additions & 0 deletions source-code/website/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
"@zag-js/solid": "^0.2.1",
"compression": "^1.7.4",
"cookie-session": "^2.0.0",
"cors": "^2.8.5",
"date-fns": "^2.29.3",
"dotenv": "^16.0.3",
"express": "^4.18.2",
Expand All @@ -40,6 +41,8 @@
"shiki": "^0.11.1",
"sirv": "^2.0.2",
"solid-js": "^1.6.9",
"supertokens-node": "^12.1.5",
"supertokens-web-js": "^0.3.0",
"telefunc": "^0.1.41",
"yaml": "^2.1.3",
"zod": "^3.19.1"
Expand All @@ -51,12 +54,15 @@
"@tailwindcss/forms": "^0.5.3",
"@tailwindcss/typography": "^0.5.7",
"@types/compression": "^1.7.2",
"@types/cookie": "^0.5.1",
"@types/cookie-session": "^2.0.44",
"@types/cors": "^2.8.13",
"@types/express": "^4.17.14",
"@types/lodash-es": "^4.17.6",
"@types/marked": "^4.0.8",
"autoprefixer": "^10.4.12",
"babel-preset-solid": "^1.6.2",
"cookie": "^0.5.0",
"fast-glob": "^3.2.12",
"postcss": "^8.4.18",
"rollup-plugin-node-polyfills": "^0.2.1",
Expand Down
2 changes: 1 addition & 1 deletion source-code/website/src/pages/editor/index.page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ export function Page() {
/**
* A card that displays a repository.
*/
function RepositoryCard(props: { repository: (typeof repositories)[number] }) {
function RepositoryCard(props: { repository: typeof repositories[number] }) {
const isExampleRepository = () =>
props.repository.owner === "inlang" &&
props.repository.repository === "example";
Expand Down
5 changes: 5 additions & 0 deletions source-code/website/src/renderer/_default.page.client.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,11 @@ import "@shoelace-style/shoelace/dist/components/button-group/button-group.js";
import "@shoelace-style/shoelace/dist/components/spinner/spinner.js";

import { clientSideEnv } from "@env";
import { initClientSession } from "@src/services/auth/lib/session/client.js";

// Initialize the session logic.
// This has to happen before the first API calls to routes that require the session logic are made.
await initClientSession();

// enable error logging via sentry in production
if (import.meta.env.PROD) {
Expand Down
70 changes: 42 additions & 28 deletions source-code/website/src/server/git-proxy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,41 +9,55 @@
*/

// import { assert } from "@src/services/assert/index.js";
import type { NextFunction, Request, Response } from "express";
import { Router } from "express";
import { serverSideEnv } from "@env";
// @ts-ignore
import createMiddleware from "@isomorphic-git/cors-proxy/middleware.js";
import { decryptAccessToken } from "@src/services/auth/logic.js";
import { verifyInlangSession } from "@src/services/auth/lib/session/server.js";
import type { InlangSessionRequest } from "@src/services/auth/lib/session/types.server.js";

const middleware = createMiddleware({});
const env = await serverSideEnv();

export async function proxy(
request: Request,
response: Response,
next: NextFunction
) {
// TODO enable after https://github.com/brillout/vite-plugin-ssr/discussions/560#discussioncomment-4420315
// TODO currently not using vite to bundle this file, hence the call below will not be pruned
// assert(request.url.startsWith(env.VITE_GIT_REQUEST_PROXY_PATH));
if (request.path.includes("github") === false) {
response.status(500).send("Unsupported git hosting provider.");
}
try {
// decrypt the access token
const encryptedAccessToken = request.session?.encryptedAccessToken;
if (encryptedAccessToken) {
const decryptedAccessToken = await decryptAccessToken({
JWE_SECRET_KEY: env.JWE_SECRET_KEY,
jwe: encryptedAccessToken,
});
// set the authorization header (must be base64 encoded)
request.headers["authorization"] = `Basic ${btoa(decryptedAccessToken)}`;
export const gitProxyRouter = Router();

// forward git requests to the proxy with wildcard `*`.
gitProxyRouter.all(
env.VITE_GIT_REQUEST_PROXY_PATH + "*",
verifyInlangSession({ sessionRequired: false }),
async (request: InlangSessionRequest, response, next) => {
// TODO enable after https://github.com/brillout/vite-plugin-ssr/discussions/560#discussioncomment-4420315
// TODO currently not using vite to bundle this file, hence the call below will not be pruned
// assert(request.url.startsWith(env.VITE_GIT_REQUEST_PROXY_PATH));

if (request.path.includes("github") === false) {
response.status(500).send("Unsupported git hosting provider.");
}
try {
// decrypt the access token if it exists

let sessionData;
if (request.session != undefined) {
sessionData = await request.session.getSessionData();
}

const encryptedAccessToken = sessionData?.encryptedAccessToken;
if (encryptedAccessToken) {
const decryptedAccessToken = await decryptAccessToken({
JWE_SECRET_KEY: env.JWE_SECRET_KEY,
jwe: encryptedAccessToken,
});
// set the authorization header (must be base64 encoded)
request.headers["authorization"] = `Basic ${btoa(
decryptedAccessToken
)}`;
}
// remove the proxy path from the url
request.url = request.url.slice(env.VITE_GIT_REQUEST_PROXY_PATH.length);
middleware(request, response, next);
} catch (error) {
next(error);
}
// remove the proxy path from the url
request.url = request.url.slice(env.VITE_GIT_REQUEST_PROXY_PATH.length);
middleware(request, response, next);
} catch (error) {
next(error);
}
}
);
Loading

0 comments on commit ffab344

Please sign in to comment.