add support for git operation through ssh@@ -10,7 +10,7 @@ package-lock.json
# Ignore built public files
public/.islands/
-public/instant-router.js
+# public/instant-router.js
public/instant-router.js.map
public/islands-runtime.js
public/islands-runtime.js.map
@@ -85,6 +85,79 @@ RUN git rev-parse HEAD > .gitstamp
COPY ./.gitstamp /app/.gitstamp
RUN rm -rf /app/.git
+# Install required dependencies
+RUN apt-get update -y && apt-get install git-core openssh-server gnupg sudo curl jq ca-certificates -y
+RUN mkdir -p /etc/apt/keyrings && \
+ curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key \
+ | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg && \
+ echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_20.x nodistro main" \
+ | tee /etc/apt/sources.list.d/nodesource.list && \
+ apt-get -qq update && \
+ apt-get -qq -y install --no-install-recommends \
+ nodejs=$(apt-cache show nodejs | grep -F 'Version: 20.0.0' | cut -f 2 -d ' ')
+
+# Add git-shell to system shells
+RUN echo "/usr/bin/git-shell" >> /etc/shells
+# RUN chsh -s /usr/bin/git-shell
+
+# Create git user
+RUN adduser git
+# RUN usermod -u 1000 git
+
+# Change git user shell to use git-shell
+# RUN usermod --shell /usr/bin/git-shell git
+RUN usermod --shell /usr/bin/sh git
+
+# Setup git user home repos' folder
+RUN mkdir /home/git/repos
+RUN chown git:git -R /home/git/repos
+RUN usermod --home /home/git/repos git
+
+# Make it possible for git user to chsh
+RUN sed -i -E 's/auth required pam_shells.so/auth sufficient pam_shells.so/' /etc/pam.d/chsh
+# Enable Password-less SSH Authentication (private-keys only)
+RUN sed -i -E 's/#?PasswordAuthentication yes/PasswordAuthentication no/' /etc/ssh/sshd_config
+# RUN echo "ForceCommand /usr/bin/ssh_command" >> /etc/ssh/sshd_config
+RUN echo "AllowUsers root git" >> /etc/ssh/sshd_config
+RUN echo "AuthorizedKeysFile .ssh/authorized_keys /home/git/.ssh/authorized_keys" >> /etc/ssh/sshd_config
+
+# Empty machine motd
+RUN sed -i -E 's|session optional pam_motd.so motd=/run/motd.dynamic|#session optional pam_motd.so motd=/run/motd.dynamic|' /etc/pam.d/sshd
+RUN sed -i -E 's|session optional pam_motd.so noupdate|#session optional pam_motd.so noupdate|' /etc/pam.d/sshd
+RUN echo "" > /etc/motd
+
+# Change to git user home dir
+WORKDIR /home/git/
+
+# Add git-shell command no-interactive-login
+RUN mkdir git-shell-commands/
+COPY ./data/git-shell-commands/no-interactive-login /home/git/git-shell-commands/no-interactive-login
+RUN chown git:git git-shell-commands/no-interactive-login
+RUN chmod +x git-shell-commands/no-interactive-login
+
+# Add ssh command to force client command
+COPY ./data/ssh_command /usr/bin/
+COPY ./data/ssh_command_node /usr/bin/
+RUN chmod +x /usr/bin/ssh_command
+RUN chmod +x /usr/bin/ssh_command_node
+
+# Setup ssh folder and keys
+RUN mkdir -p .ssh
+RUN chmod 700 .ssh
+RUN touch .ssh/authorized_keys
+COPY ./data/authorized_keys .ssh/authorized_keys
+RUN chmod 600 .ssh/authorized_keys
+RUN chown git:git -R .ssh
+
+# Switch to root user
+USER root
+WORKDIR /home/git
+
+RUN service ssh start
+
+EXPOSE 22
EXPOSE ${PORT}
-CMD ["node", "app/server.js"]
+WORKDIR /app
+
+CMD ["/bin/bash", "-c", "/usr/sbin/sshd -D & nohup; node app/server.js"]
@@ -0,0 +1,66 @@
+# syntax=docker/dockerfile:1
+FROM debian:12
+
+# Install required dependencies
+RUN apt-get update -y && apt-get install git-core openssh-server sudo curl -y
+RUN curl -fsSL https://deb.nodesource.com/setup_16.x | sudo -E bash -
+RUN apt-get install nodejs -y
+
+# Add git-shell to system shells
+RUN echo "/usr/bin/git-shell" >> /etc/shells
+# RUN chsh -s /usr/bin/git-shell
+
+# Create git user
+RUN adduser git
+RUN usermod -u 1000 git
+
+# Change git user shell to use git-shell
+# RUN usermod --shell /usr/bin/git-shell git
+RUN usermod --shell /usr/bin/sh git
+
+# Setup git user home repos' folder
+RUN mkdir /home/git/repos
+RUN chown git:git -R /home/git/repos
+RUN usermod --home /home/git/repos git
+
+# Make it possible for git user to chsh
+RUN sed -i -E 's/auth required pam_shells.so/auth sufficient pam_shells.so/' /etc/pam.d/chsh
+# Enable Password-less SSH Authentication (private-keys only)
+RUN sed -i -E 's/#?PasswordAuthentication yes/PasswordAuthentication no/' /etc/ssh/sshd_config
+# RUN echo "ForceCommand /usr/bin/ssh_command" >> /etc/ssh/sshd_config
+RUN echo "AllowUsers root git" >> /etc/ssh/sshd_config
+RUN echo "AuthorizedKeysFile .ssh/authorized_keys /home/git/.ssh/authorized_keys" >> /etc/ssh/sshd_config
+
+# Empty machine motd
+RUN sed -i -E 's|session optional pam_motd.so motd=/run/motd.dynamic|#session optional pam_motd.so motd=/run/motd.dynamic|' /etc/pam.d/sshd
+RUN sed -i -E 's|session optional pam_motd.so noupdate|#session optional pam_motd.so noupdate|' /etc/pam.d/sshd
+RUN echo "" > /etc/motd
+
+# Change to git user home dir
+WORKDIR /home/git/
+
+# Add git-shell command no-interactive-login
+RUN mkdir git-shell-commands/
+COPY ./data/git-shell-commands/no-interactive-login /home/git/git-shell-commands/no-interactive-login
+RUN chown git:git git-shell-commands/no-interactive-login
+RUN chmod +x git-shell-commands/no-interactive-login
+
+# Add ssh command to force client command
+COPY ./data/ssh_command /usr/bin/
+RUN chmod +x /usr/bin/ssh_command
+
+# Setup ssh folder and keys
+RUN mkdir -p .ssh
+RUN chmod 700 .ssh
+COPY ./data/authorized_keys .ssh/authorized_keys
+RUN chmod 600 .ssh/authorized_keys
+RUN chown git:git -R .ssh
+
+# Switch to root user
+USER root
+WORKDIR /home/git
+
+RUN service ssh start
+EXPOSE 22
+
+CMD ["/usr/sbin/sshd","-D"]
@@ -1,11 +1,11 @@
{
- "_generatedAtUnix": 1702180342118,
+ "_generatedAtUnix": 1702820538258,
"_hashAlgorithm": "sha1",
"_version": 2,
"assets": {
"IslandsRuntime": {
"kind": "generated",
- "hash": "81cd1baaa8b7c287f98b563607aaeeaa2884158a",
+ "hash": "3fee654387a4e627cbd5ee9b35238f9157ea7eac",
"path": "./public/instant-router.js"
},
"InstantRouter": {
@@ -16,7 +16,7 @@
},
"islands": {
"Code": {
- "hash": "c3c638cfcd010020f6ca251f7a3984721b60048d",
+ "hash": "4b585b9dc0f2dcd0dacbfed17fcf8c0c4f40e0e8",
"pathSource": "./app/islands/Code.tsx",
"pathBundle": "./public/.islands/Code.bundle.js",
"pathSourceMap": "./public/.islands/Code.bundle.js.map"
@@ -40,7 +40,7 @@
"pathSourceMap": "./public/.islands/RepositoriesList.bundle.js.map"
},
"RepositoryCommitSummaryLine": {
- "hash": "11d260b4acaf7aca6fc2b746529617093f3a3356",
+ "hash": "f1b583339bcae5d3a207139106daee95924db9b9",
"pathSource": "./app/islands/RepositoryCommitSummaryLine.tsx",
"pathBundle": "./public/.islands/RepositoryCommitSummaryLine.bundle.js",
"pathSourceMap": "./public/.islands/RepositoryCommitSummaryLine.bundle.js.map"
@@ -52,7 +52,7 @@
"pathSourceMap": "./public/.islands/RepositoryCreateForm.bundle.js.map"
},
"RepositoryFilesDiffsList": {
- "hash": "fd96b0001b9db83675724042cfc0d0c8323fd07b",
+ "hash": "5a450fca443764deb2043fd1b70264cd7646e7ba",
"pathSource": "./app/islands/RepositoryFilesDiffsList.tsx",
"pathBundle": "./public/.islands/RepositoryFilesDiffsList.bundle.js",
"pathSourceMap": "./public/.islands/RepositoryFilesDiffsList.bundle.js.map"
@@ -138,7 +138,7 @@
"pathSource": "./app/views/repository/RepositoryForkView.tsx"
},
"RepositoryShowObjectView": {
- "hash": "ee4a0c034ffbbc9ae6b257dca2121d629d7049bf",
+ "hash": "cf63c6b6caaac822f36ddf17e663c705b10b4aa6",
"pathSource": "./app/views/repository/RepositoryShowObjectView.tsx"
},
"RepositoryPullRequestCreateView": {
@@ -0,0 +1,8 @@
+import React, { PropsWithChildren } from "react";
+
+// import { AppRoute } from "./routes.defs";
+// import HomeView from "./views/HomeView";
+
+export function App({ children }: PropsWithChildren<{}>) {
+ return <>{children}</>;
+}
@@ -0,0 +1,24 @@
+import Color from "color";
+import styled, { css } from "styled-components";
+
+export const Chip = styled.div<{ color?: string }>`
+ ${({ color = undefined }) => css`
+ display: flex;
+ flex-flow: row nowrap;
+ align-items: center;
+ justify-content: center;
+
+ padding: 3px 8px;
+ font-size: 14px;
+ line-height: 14px;
+
+ font-weight: bold;
+ text-transform: uppercase;
+
+ color: ${color ? Color(color).alpha(1).toString() : "black"};
+ background-color: ${color
+ ? Color(color).alpha(0.3).toString()
+ : "rgba(0, 0, 0, 0.3)"};
+ border-radius: 8px;
+ `};
+`;
@@ -6,3 +6,4 @@ export { RepositoryPullRequestsController } from "./repositoryPullRequests";
export { SyntaxHighlightController } from "./syntaxHighlight";
export { ThemeController } from "./theme";
export { UserController } from "./user";
+export { SSHAuthController } from "./ssh-auth";
@@ -0,0 +1,79 @@
+// 3rd-party
+import type { ReqHandler } from "@ethicdevs/react-monolith";
+
+import { GitServer } from "@ethicdevs/fastify-git-server";
+import { AppRoute, AppRouteParams } from "../routes.defs";
+import { makeGitServerService } from "../services/gitServer";
+
+const onSSHAuth: ReqHandler<AppRouteParams, AppRoute.SSH_AUTH> = async (
+ request,
+ reply
+) => {
+ const gitService = makeGitServerService({
+ request,
+ cryptoService: request.cryptoService,
+ });
+
+ // console.log("request:", request);
+
+ request.body =
+ typeof request.body === "string" ? JSON.parse(request.body) : request.body;
+
+ const { command, repoSlug, username, publicKey } = request.body;
+
+ console.log("command:", command);
+ console.log("repoSlug:", repoSlug);
+ console.log("username:", username);
+ console.log("publicKey:", publicKey);
+
+ const result = await gitService.repositoryResolver(
+ repoSlug.replace(/\.git$/, "")
+ );
+
+ let { authMode, gitRepositoryDir } = result;
+ gitRepositoryDir = gitRepositoryDir.toString().endsWith(".git")
+ ? gitRepositoryDir
+ : `${gitRepositoryDir}.git`;
+
+ if (
+ authMode === GitServer.AuthMode.NEVER ||
+ (authMode === GitServer.AuthMode.PUSH_ONLY &&
+ command !== "git-receive-pack") // push
+ ) {
+ console.log(
+ "no need for auth, repo is public/push_only and command is not push"
+ );
+
+ reply.status(200).send({
+ success: true,
+ authMode,
+ command,
+ gitRepositoryDir,
+ });
+ return;
+ }
+
+ const isAuthorizationValid = await gitService.authorizationResolver(
+ repoSlug.replace(/\.git$/, "") + ".pub",
+ {
+ username,
+ password: publicKey,
+ }
+ );
+
+ console.log(
+ "authorization result:",
+ isAuthorizationValid ? "valid" : "invalid"
+ );
+
+ reply.status(isAuthorizationValid ? 200 : 400).send({
+ success: isAuthorizationValid,
+ authMode,
+ command,
+ gitRepositoryDir,
+ });
+};
+
+export const SSHAuthController = {
+ onSSHAuth,
+};
@@ -1,7 +1,13 @@
// 1st-party
import { ReactIsland } from "@ethicdevs/react-monolith";
// 3rd-party
-import React, { useCallback, useEffect, useMemo, useState } from "react";
+import React, {
+ CSSProperties,
+ useCallback,
+ useEffect,
+ useMemo,
+ useState,
+} from "react";
import Prism from "prismjs";
import styled, { css } from "styled-components";
// import { fetch } from "cross-fetch";
@@ -15,6 +21,7 @@ import { ClientSideRouterEvents } from "./InstantRouterIndicator";
interface CodeProps {
code: string;
language: string;
+ style?: CSSProperties;
[x: string]: unknown;
}
@@ -40,6 +47,7 @@ const Code: ReactIsland<CodeProps & WithThemeSchemeProp> = ({
code,
language,
themeScheme,
+ style,
...props
}) => {
const { before: lineStartAt } = useMemo(
@@ -161,7 +169,7 @@ const Code: ReactIsland<CodeProps & WithThemeSchemeProp> = ({
data-start={lineStartAt}
className={` language-${language} line-numbers`}
themeScheme={themeScheme}
- style={{ counterReset: "linenumber 0" }}
+ style={{ counterReset: "linenumber 0", ...(style || {}) }}
>
<StyledCodeTag {...props} dangerouslySetInnerHTML={innerHtml} />
</StylePreTag>
@@ -13,109 +13,110 @@ export interface RepositoryCommitSummaryLineProps {
currentRef: string;
orgSlug: string;
repoSlug: string;
+ defaultFullSubjectVisible?: boolean;
}
const MAX_COMMIT_LINE_LENGTH = 60;
const TRAILING_CHAR = " ...";
const TRAILING_CHAR_LENGTH = TRAILING_CHAR.length;
-const RepositoryCommitSummaryLine: ReactIsland<RepositoryCommitSummaryLineProps> =
- ({ orgSlug, repoSlug, commit }) => {
- const [isFullSubjectShown, setIsFullSubjectShown] =
- useState<boolean>(false);
+const RepositoryCommitSummaryLine: ReactIsland<
+ RepositoryCommitSummaryLineProps
+> = ({ orgSlug, repoSlug, commit, defaultFullSubjectVisible = false }) => {
+ const [isFullSubjectShown, setIsFullSubjectShown] = useState<boolean>(
+ defaultFullSubjectVisible
+ );
- const toggleFullSubjectVisibility = useCallback(
- (ev: React.MouseEvent<HTMLSpanElement>) => {
- ev.preventDefault();
- setIsFullSubjectShown((prevVisibility) => !prevVisibility);
- },
- [setIsFullSubjectShown]
- );
+ const toggleFullSubjectVisibility = useCallback(
+ (ev: React.MouseEvent<HTMLSpanElement>) => {
+ ev.preventDefault();
+ setIsFullSubjectShown((prevVisibility) => !prevVisibility);
+ },
+ [setIsFullSubjectShown]
+ );
- const isSubjectTooLongForDisplay = useMemo(
- () => commit.subject.length > MAX_COMMIT_LINE_LENGTH,
- [commit.subject.length]
- );
+ const isSubjectTooLongForDisplay = useMemo(
+ () => commit.subject.length > MAX_COMMIT_LINE_LENGTH,
+ [commit.subject.length]
+ );
- const subject = useMemo(
- () =>
- isSubjectTooLongForDisplay
- ? `${commit.subject.substring(
- 0,
- MAX_COMMIT_LINE_LENGTH - TRAILING_CHAR_LENGTH
- )}`
- : commit.subject,
- [commit.subject]
- );
+ const subject = useMemo(
+ () =>
+ isSubjectTooLongForDisplay
+ ? `${commit.subject.substring(
+ 0,
+ MAX_COMMIT_LINE_LENGTH - TRAILING_CHAR_LENGTH
+ )}`
+ : commit.subject,
+ [commit.subject]
+ );
- return (
- <Grid.Col fluid nowrap>
- <Grid.Row
- gap={8}
- alignItems={"stretch"}
- style={{ flexWrap: "wrap-reverse" }}
+ return (
+ <Grid.Col fluid nowrap>
+ <Grid.Row
+ gap={8}
+ alignItems={"stretch"}
+ style={{ flexWrap: "wrap-reverse" }}
+ >
+ <Grid.Col flex={"1 0 calc(100% - 220px)"} style={{ minWidth: 360 }}>
+ <strong>{commit.author.name}</strong>
+ <span style={{ marginTop: 8 }}>
+ <a
+ href={buildRouteLink(AppRoute.REPOSITORY_SHOW_OBJECT, {
+ orgSlug,
+ repoSlug,
+ objectId: commit.commit,
+ })}
+ >
+ {subject}
+ </a>
+ {isSubjectTooLongForDisplay ? (
+ <span onClick={toggleFullSubjectVisibility}>{TRAILING_CHAR}</span>
+ ) : null}
+ </span>
+ </Grid.Col>
+ <Grid.Col
+ alignItems={"flex-end"}
+ style={{
+ textAlign: "right",
+ flex: "1 0 200px",
+ width: 200,
+ minWidth: 200,
+ }}
>
- <Grid.Col flex={"1 0 calc(100% - 220px)"} style={{ minWidth: 360 }}>
- <strong>{commit.author.name}</strong>
- <span style={{ marginTop: 8 }}>
+ <span>
+ <a
+ href={buildRouteLink(AppRoute.REPOSITORY_SHOW_OBJECT, {
+ orgSlug,
+ repoSlug,
+ objectId: commit.commit,
+ })}
+ >
+ {commit.abbreviated_commit}
+ </a>
+ {commit.abbreviated_parent.trim() != "" ? (
<a
href={buildRouteLink(AppRoute.REPOSITORY_SHOW_OBJECT, {
orgSlug,
repoSlug,
- objectId: commit.commit,
+ objectId: commit.parent,
})}
>
- {subject}
+ {` (parent ${commit.abbreviated_parent})`}
</a>
- {isSubjectTooLongForDisplay ? (
- <span onClick={toggleFullSubjectVisibility}>
- {TRAILING_CHAR}
- </span>
- ) : null}
- </span>
- </Grid.Col>
- <Grid.Col
- alignItems={"flex-end"}
- style={{
- textAlign: "right",
- flex: "1 0 200px",
- width: 200,
- minWidth: 200,
- }}
- >
- <span>
- <a
- href={buildRouteLink(AppRoute.REPOSITORY_SHOW_OBJECT, {
- orgSlug,
- repoSlug,
- objectId: commit.commit,
- })}
- >
- {commit.abbreviated_commit}
- </a>
- {commit.abbreviated_parent.trim() != "" ? (
- <a
- href={buildRouteLink(AppRoute.REPOSITORY_SHOW_OBJECT, {
- orgSlug,
- repoSlug,
- objectId: commit.parent,
- })}
- >
- {` (parent ${commit.abbreviated_parent})`}
- </a>
- ) : null}
- </span>
- <span style={{ marginTop: 8 }}>
- {new Date(commit.author.date).toLocaleString()}
- </span>
- </Grid.Col>
- </Grid.Row>
- {isFullSubjectShown ? (
- <code style={{ marginTop: 8 }}>{commit.subject}</code>
- ) : null}
- </Grid.Col>
- );
- };
+ ) : null}
+ </span>
+ <span style={{ marginTop: 8 }}>
+ {new Date(commit.author.date).toLocaleString()}
+ </span>
+ </Grid.Col>
+ </Grid.Row>
+ {isFullSubjectShown ? (
+ <code style={{ marginTop: 8 }}>{commit.subject}</code>
+ ) : null}
+ </Grid.Col>
+ );
+};
RepositoryCommitSummaryLine.displayName = "RepositoryCommitSummaryLine";
export default RepositoryCommitSummaryLine;
@@ -11,7 +11,9 @@ import type {
import { AppRoute } from "../routes.defs";
import { Const } from "../const";
import { Card } from "../components/Card.styled";
+import { Chip } from "../components/Chip";
import { Grid } from "../components/Grid";
+import { NamedColors } from "../utils/style";
import { buildRouteLink } from "../utils/shared";
// app islands
import Code, { getThemedCodeCss } from "../islands/Code";
@@ -41,28 +43,46 @@ const RepositoryFilesDiffsList: ReactIsland<
{filesDiffs.map(({ chunks, ...diff }, idx) => (
<Card
key={[diff.from, diff.to].join(":")}
- style={{ marginTop: idx > 0 ? 16 : 0, width: "100%" }}
+ style={{ marginTop: idx > 0 ? 16 : 0, width: "100%", padding: 8 }}
themeScheme={themeScheme}
>
<Grid.Col fluid nowrap>
- <Grid.Row fluid nowrap>
- <strong>{diff.from}</strong>
- <span style={{ marginLeft: 16 }}>{" -> "}</span>
- <strong style={{ marginLeft: 16 }}>{diff.to}</strong>
+ <Grid.Row fluid nowrap gap={12} alignItems={"center"}>
+ {diff.from === "/dev/null" ? (
+ <Chip color={"rgb(43, 176, 90)"}>new file</Chip>
+ ) : diff.to !== "/dev/null" ? (
+ <strong>{diff.from}</strong>
+ ) : null}
+ {diff.to !== diff.from && (
+ <>
+ {diff.to !== "/dev/null" && diff.from !== "/dev/null" && (
+ <span>{" -> "}</span>
+ )}
+ {diff.to === "/dev/null" ? (
+ <>
+ <Chip color={"rgb(215, 44, 44)"}>file deleted</Chip>
+ <strong>{diff.from}</strong>
+ </>
+ ) : (
+ <strong>{diff.to}</strong>
+ )}
+ </>
+ )}
</Grid.Row>
<Grid.Row
fluid
nowrap
alignItems={"center"}
style={{ marginTop: 8 }}
+ gap={16}
>
- <div>
- <strong>additions:</strong> <span>{diff.additions}</span>
+ <div style={{ color: "rgb(43, 176, 90)" }}>
+ <strong>+</strong> <span>{diff.additions}</span>
</div>
- <div style={{ marginLeft: 16 }}>
- <strong>deletions:</strong> <span>{diff.deletions}</span>
+ <div style={{ color: "rgb(215, 44, 44)" }}>
+ <strong>-</strong> <span>{diff.deletions}</span>
</div>
- <div style={{ marginLeft: 16 }}>
+ <div>
<a
href={buildRouteLink(
AppRoute.REPOSITORY_BROWSER_WITH_PATH,
@@ -70,7 +90,7 @@ const RepositoryFilesDiffsList: ReactIsland<
orgSlug,
repoSlug,
currentRef: commitHash,
- "*": diff.to,
+ "*": diff.to === "/dev/null" ? diff.from : diff.to,
}
)}
>
@@ -83,7 +103,7 @@ const RepositoryFilesDiffsList: ReactIsland<
orgSlug,
repoSlug,
currentRef: Const.PRIMARY_BRANCH_REF,
- "*": diff.to,
+ "*": diff.to === "/dev/null" ? diff.from : diff.to,
}
)}
style={{ marginLeft: 16 }}
@@ -104,7 +124,30 @@ const RepositoryFilesDiffsList: ReactIsland<
code={getChunkContent(chunk)}
language={"diff"}
themeScheme={themeScheme}
+ style={{
+ borderTopLeftRadius: subIdx === 0 ? 8 : 0,
+ borderTopRightRadius: subIdx === 0 ? 8 : 0,
+ borderBottomLeftRadius:
+ subIdx === chunks.length - 1 ? 8 : 0,
+ borderBottomRightRadius:
+ subIdx === chunks.length - 1 ? 8 : 0,
+ }}
/>
+ {subIdx < chunks.length - 1 && (
+ <Grid.Row
+ fluid
+ alignItems={"center"}
+ style={{
+ height: 30,
+ width: "100%",
+ border: `1px solid ${NamedColors.BORDER_DEFAULT[themeScheme]}`,
+ padding: "0 10px",
+ opacity: 0.7,
+ }}
+ >
+ ...
+ </Grid.Row>
+ )}
</div>
))}
</Grid.Col>
@@ -9,6 +9,7 @@ import { PullRequestFormState } from "./islands/RepositoryPullRequestCreateForm"
export enum AppRoute {
HOME = "home",
+ SSH_AUTH = "ssh.auth_helper",
THEME_SET_SCHEME_ACTION = "theme.set_scheme.action",
AUTH_REGISTER = "auth.register",
AUTH_REGISTER_ACTION = "auth.register.action",
@@ -46,6 +47,7 @@ export enum AppRoute {
export const AppRoutePaths: Record<AppRoute, string> = {
[AppRoute.HOME]: "/",
+ [AppRoute.SSH_AUTH]: "/_ssh/auth",
[AppRoute.THEME_SET_SCHEME_ACTION]: "/theme/:themeScheme",
[AppRoute.AUTH_REGISTER]: "/auth/register",
[AppRoute.AUTH_REGISTER_ACTION]: "/auth/register",
@@ -95,6 +97,15 @@ export const AppRoutePaths: Record<AppRoute, string> = {
export interface AppRouteParams {
[x: string]: any;
[AppRoute.HOME]: undefined;
+ [AppRoute.SSH_AUTH]: {
+ body: {
+ // git-receive-pack: push, git-upload-pack: clone, pull, fetch
+ command: "git-receive-pack" | "git-upload-pack";
+ repoSlug: string;
+ username: string;
+ publicKey: string;
+ };
+ };
[AppRoute.THEME_SET_SCHEME_ACTION]: {
params: {
themeScheme: AppThemeScheme;
@@ -331,6 +342,29 @@ export interface AppRouteParams {
export const AppRouteSchemas: Record<AppRoute, undefined | FastifySchema> = {
[AppRoute.HOME]: undefined,
+ [AppRoute.SSH_AUTH]: {
+ body: {
+ type: "object",
+ required: ["command", "repoSlug", "username", "publicKey"],
+ additionalProperties: false,
+ properties: {
+ // git-receive-pack: push, git-upload-pack: clone, pull, fetch
+ command: {
+ type: "string",
+ enum: ["git-receive-pack", "git-upload-pack"],
+ },
+ repoSlug: {
+ type: "string",
+ },
+ username: {
+ type: "string",
+ },
+ publicKey: {
+ type: "string",
+ },
+ },
+ },
+ },
[AppRoute.THEME_SET_SCHEME_ACTION]: {
params: {
type: "object",
@@ -25,6 +25,7 @@ import {
OrganizationController,
RepositoryController,
RepositoryPullRequestsController,
+ SSHAuthController,
SyntaxHighlightController,
ThemeController,
UserController,
@@ -65,6 +66,12 @@ const RootAppRouter: AppRouter<AppRouteParams> = () => {
preHandler={guestOrDashboardRedirect}
handler={HomeController.getHomeView}
/>
+ <Route
+ name={AppRoute.SSH_AUTH}
+ method={"POST"}
+ path={AppRoutePaths[AppRoute.SSH_AUTH]}
+ handler={SSHAuthController.onSSHAuth}
+ />
{/* --- */}
<Route
name={AppRoute.AUTH_REGISTER}
@@ -28,6 +28,7 @@ import InternalErrorView, {
InternalErrorViewProps,
} from "./views/InternalErrorView";
// app
+import { App } from "./App";
import { AppRouteParams } from "./routes.defs";
import { Const } from "./const";
import { Env } from "./env";
@@ -75,7 +76,7 @@ async function main(): Promise<AppServer> {
featureFlags: {
withIncrementalBuild: true,
withImportsMap: true,
- withInstantRouter: true,
+ withInstantRouter: false,
withStyledSSR: true,
withTreeShaking: true,
withDefaultErrorHandlers: false,
@@ -88,6 +89,9 @@ async function main(): Promise<AppServer> {
routesFile: Paths.ROUTES_FILE,
viewsFolder: Paths.VIEWS_FOLDER,
},
+ specialComponents: {
+ AppComponent: App,
+ },
externalDependencies: {
"cross-fetch": "CrossFetch",
"markdown-to-jsx": "MarkdownToJSX",
@@ -274,6 +278,12 @@ async function main(): Promise<AppServer> {
} as any,
});
+ s.decorateRequest("gitService", {
+ get() {
+ return gitService;
+ },
+ });
+
// register the Git Server plugin and bind the resolvers/callbacks
// to the gitService instance made above
s.register(fastifyGitServer, {
@@ -11,9 +11,13 @@ const makeAuthorizationResolver: ServiceMethodFactory<
PromiseLike<boolean>
> = ({ cryptoService, request }) => {
return async (repoPath, { username, password }) => {
- const [orgSlug, repoSlug] = repoPath.split("/");
+ const [orgSlug, repoSlugUnsafe] = repoPath.split("/");
+
+ // password contains the publicKey instead of regular password
+ const isPubKeyAuth = repoSlugUnsafe.endsWith(".pub");
+
+ const repoSlug = repoSlugUnsafe.replace(/\.(git|pub)$/, "");
- const hashedPassword = cryptoService.computeHash(password);
const user = await request.prisma.user.findUnique({
where: {
username,
@@ -51,16 +55,37 @@ const makeAuthorizationResolver: ServiceMethodFactory<
return false;
}
+ const repoOrgOwnerIsReqUser = org.ownerId === user.id;
+ const repoMembershipsHasReqUser =
+ org.memberships.find((m) => m.id === user.id) != null;
+
if (repo.visibility === ResourceVisibility.PUBLIC) {
return true;
} else {
- // TODO:
- // allow read-only for unlisted users without auth, but write behind auth.
- return !!(
- (org.ownerId === user.id ||
- org.memberships.find((m) => m.id === user.id)) &&
- hashedPassword === user.hashedPassword
- );
+ // TODO: allow read-only for unlisted users without auth, but write behind auth.
+ if (
+ repoOrgOwnerIsReqUser === false &&
+ repoMembershipsHasReqUser === false
+ ) {
+ return false;
+ }
+
+ if (isPubKeyAuth) {
+ const matchingPk = await request.prisma.userSSHKey.findFirst({
+ where: {
+ // password contains the publicKey instead of regular password
+ key: password,
+ },
+ include: {
+ user: true,
+ },
+ });
+
+ return matchingPk != null && matchingPk.user.id === user.id;
+ } else {
+ const hashedPassword = cryptoService.computeHash(password);
+ return hashedPassword === user.hashedPassword;
+ }
}
};
};
@@ -1,35 +1,35 @@
// 3rd-party
-import { AppServer } from "@ethicdevs/react-monolith";
+// import { AppServer } from "@ethicdevs/react-monolith";
import { preHandlerHookHandler } from "fastify";
// lib
-import { Env } from "../../env";
+// import { Env } from "../../env";
export const localAppDomainPreHandler: preHandlerHookHandler = (
- request,
- reply,
+ _request,
+ _reply,
done
) => {
- const { $port } = (request.server as AppServer).reactMonolith!;
+ // const { $port } = (request.server as AppServer).reactMonolith!;
// Safe-guard local requests to not forget because setting cookies on
// the `localhost` domain is not consistent across browsers and lead to
// testing/developing kind of "on the wrong env". To fix this we use an
// entry in the /etc/hosts file that maps to a locally known domain name
// on which we can set cookies as if we were in production mode/real domain.
- if (request.hostname === `localhost:${$port}`) {
- console.log(
- `--- REQUEST TO 'localhost' DETECTED, PLEASE USE '${Env.DEPLOYMENT_DOMAIN}' INSTEAD FOR COOKIES TO WORK! ---`
- );
- console.log(
- `--- MAKE SURE YOU HAVE '127.0.0.1 ${Env.DEPLOYMENT_DOMAIN}' SET IN YOUR '/etc/hosts' FILE! ---`
- );
- console.log(
- `--- REDIRECTED TO 'http://${Env.DEPLOYMENT_DOMAIN}:${$port}' ---`
- );
- reply.redirect(
- 301,
- `http://${Env.DEPLOYMENT_DOMAIN}:${$port}${request.url}`
- );
- }
+ // if (request.hostname === `localhost:${$port}`) {
+ // console.log(
+ // `--- REQUEST TO 'localhost' DETECTED, PLEASE USE '${Env.DEPLOYMENT_DOMAIN}' INSTEAD FOR COOKIES TO WORK! ---`
+ // );
+ // console.log(
+ // `--- MAKE SURE YOU HAVE '127.0.0.1 ${Env.DEPLOYMENT_DOMAIN}' SET IN YOUR '/etc/hosts' FILE! ---`
+ // );
+ // console.log(
+ // `--- REDIRECTED TO 'http://${Env.DEPLOYMENT_DOMAIN}:${$port}' ---`
+ // );
+ // reply.redirect(
+ // 301,
+ // `http://${Env.DEPLOYMENT_DOMAIN}:${$port}${request.url}`
+ // );
+ // }
done();
};
@@ -11,7 +11,13 @@ import type {
RepositoryObject,
RepositoryWithForkedFromRepo,
} from "../../types";
-import { Card, IslandWrapper, Layout, PageWrapper } from "../../components";
+import {
+ Card,
+ Grid,
+ IslandWrapper,
+ Layout,
+ PageWrapper,
+} from "../../components";
// app islands
import Code, { getThemedCodeCss } from "../../islands/Code";
import RepositoryCommitSummaryLine from "../../islands/RepositoryCommitSummaryLine";
@@ -34,6 +40,15 @@ const RepositoryShowObjectView: ReactView<RepositoryShowObjectViewProps> = ({
parentOrg,
repo,
}) => {
+ const totalAdditions = gitObjectDiffs?.reduce(
+ (acc, obj) => (acc += obj.additions),
+ 0
+ );
+ const totalDeletions = gitObjectDiffs?.reduce(
+ (acc, obj) => (acc += obj.deletions),
+ 0
+ );
+
return (
<Layout {...commonProps}>
<PageWrapper>
@@ -50,36 +65,42 @@ const RepositoryShowObjectView: ReactView<RepositoryShowObjectViewProps> = ({
<Card
data-islandid={`${RepositoryCommitSummaryLine.name}$$0`}
- style={{ width: "100%", marginTop: 32, padding: 8 }}
+ style={{ width: "100%", marginTop: 32, padding: 8, gap: 8 }}
themeScheme={commonProps.themeScheme}
>
<RepositoryCommitSummaryLine
+ defaultFullSubjectVisible
commit={gitObject}
currentRef={currentRef}
orgSlug={parentOrg.slug}
repoSlug={repo.slug}
/>
+ <Grid.Row fluid nowrap alignItems={"center"} gap={16}>
+ <div style={{ color: "rgb(43, 176, 90)" }}>
+ <strong>+</strong> <span>{totalAdditions}</span>
+ </div>
+ <div style={{ color: "rgb(215, 44, 44)" }}>
+ <strong>-</strong> <span>{totalDeletions}</span>
+ </div>
+ </Grid.Row>
+ {gitObject.body.trim() !== "" && (
+ <div style={{ width: "100%" }}>
+ {getThemedCodeCss(commonProps.themeScheme)}
+ <div data-islandid={`${Code.name}$$0`} style={{ width: "100%" }}>
+ <Code
+ language={"plain"}
+ code={gitObject.body}
+ themeScheme={commonProps.themeScheme}
+ />
+ </div>
+ </div>
+ )}
</Card>
- {gitObject.body.trim() !== "" && (
- <>
- {getThemedCodeCss(commonProps.themeScheme)}
- <Card
- data-islandid={`${Code.name}$$0`}
- style={{ width: "100%", marginTop: 32 }}
- themeScheme={commonProps.themeScheme}
- >
- <Code
- language={"plain"}
- code={gitObject.body}
- themeScheme={commonProps.themeScheme}
- />
- </Card>
- </>
- )}
+
{gitObjectDiffs != null && (
<Card
data-islandid={`${RepositoryFilesDiffsList.name}$$0`}
- style={{ width: "100%", marginTop: 16 }}
+ style={{ width: "100%", marginTop: 24, padding: 8 }}
themeScheme={commonProps.themeScheme}
>
<RepositoryFilesDiffsList
@@ -0,0 +1 @@
+command="ssh_command wnemencha",no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDGo9GDFyJ8RJ4F+Y4Vxl2bCl8sskRYbiSWItmoBZoYFVR2qAvUyowDY2xlDa9JaG/+zbBB6sUwUr8oC/GdSaV+zp5CTP2RxcXDW2Aup6w1/a4qiilSKORMXBWvIgjyvVvHjG0TiCfeC0PfBbXCO8FhLxP5lgTrl1kVUduM0LR4/gcH3vIrrKbORWtfAC7Bw6z3qc/X9CysPxtQZYu6+AknJ1vwUtLAH2H9cKS8uwaJ5N/k0n8Sc8ANozdpp7EyodA12nFFwvf5oPakTdm5cBnnnEIe2p+GA4nP2DyybmtIR/wttJGMs6Bmz0bXO6AfFdhcGKbzwT2qEGRX5drQj0qUI+gLSZ42/9DsGN7kr2gRDpXG2ATx+c4H2XvR3fqS1cyFq+ZmezK4l32BH/KjQMR1zfgeX2Ky46YxOLQn84PvWILmpzYPLTJ02kXFr0pjofraX2h0E/0Ke2ZBPlOUcaNZOU2dJDYn/B0GVrJ0niopvseYpVXHoTzMPYpr+ReCfAc= admin@Admins-MacBook-Pro.local
@@ -0,0 +1,4 @@
+#!/bin/sh
+printf '%s\n' "Hi $USER! You've successfully authenticated, but I do not"
+printf '%s\n' "provide interactive shell access."
+exit 128
@@ -0,0 +1,49 @@
+#!/bin/sh
+
+# set -u # exit on undefined variable
+SSH_ORIGINAL_COMMAND=${SSH_ORIGINAL_COMMAND}
+USERNAME=$1
+
+# If SSH_ORIGINAL_COMMAND is unset, simply kill term.
+if [ -z ${SSH_ORIGINAL_COMMAND+x} ]; then
+ printf '%s\n' "Hi $USER! You've successfully authenticated, but I do not"
+ printf '%s\n' "provide interactive shell access."
+ exit 128
+fi
+
+RES_JSON=$(/usr/bin/ssh_command_node "${USERNAME}")
+EXIT=$?
+
+COMMAND=$(echo "$RES_JSON" | jq -r '.command')
+AUTH_MODE=$(echo "$RES_JSON" | jq -r '.authMode')
+GIT_REPO_DIR=$(echo "$RES_JSON" | jq -r '.gitRepositoryDir')
+
+echo "AUTH_MODE: ${AUTH_MODE}" >> /home/git/ssh_commands.log
+echo "GIT_REPO_DIR: ${GIT_REPO_DIR}" >> /home/git/ssh_commands.log
+
+echo "ssh_command_node stdout: ${RES_JSON}" >> /home/git/ssh_commands.log
+echo "ssh_command_node exit code: ${EXIT}" >> /home/git/ssh_commands.log
+
+if [ "$EXIT" = "0" ]; then
+ $COMMAND $GIT_REPO_DIR;
+ RESULT=$?
+
+ echo "result => ${RESULT}" >> /home/git/ssh_commands.log
+ exit $?
+else
+ echo "Could not complete request."
+ exit 1
+fi
+
+# If we should reject:
+
+
+# Assuming bash will only execute the first command in the string
+# TODO See this https://unix.stackexchange.com/a/444949/309572
+# {
+# $SSH_ORIGINAL_COMMAND
+# exit $?
+# } || { # catch
+# echo "Could not complete request."
+# exit 1
+# }
@@ -0,0 +1,101 @@
+#!/usr/bin/node
+
+const fs = require("fs");
+const cp = require("child_process");
+
+async function main(args, sshOriginalCommand) {
+ const [_, __, username] = args;
+
+ if (username == null || username.trim() === "") {
+ console.log(
+ `Hi ${process.env.USER}!\nLooks like we could not find your username.`
+ );
+ process.exit(128);
+ }
+
+ if (sshOriginalCommand == null) {
+ console.log(
+ `Hi ${process.env.USER}!\nYou've successfully authenticated, but I do not provide interactive shell access.`
+ );
+ process.exit(128);
+ }
+
+ const authorizedKeysBuffer = fs.readFileSync(
+ "/home/git/.ssh/authorized_keys",
+ { encoding: "utf8" }
+ );
+
+ const authKeys = authorizedKeysBuffer
+ .split("\n")
+ .map((line) =>
+ line.startsWith("#")
+ ? { type: "comment", text: line }
+ : line.trim() !== ""
+ ? { type: "key", text: line }
+ : null
+ )
+ .filter((x) => x != null && x.type === "key");
+
+ const pk = authKeys.find((key) =>
+ key.text.includes(`command="ssh_command ${username}"`)
+ )?.text;
+
+ const sshRsaIndex = pk.indexOf("ssh-rsa");
+ const publicKey = pk.substring(sshRsaIndex);
+
+ const [command, repoSlug] = sshOriginalCommand
+ .split(" ")
+ .map((part) => part.replace(/\'/g, "").trim());
+
+ fs.appendFileSync(
+ "/home/git/ssh_commands.log",
+ `username: ${username}\npublicKey: ${publicKey}\ncommand: ${command}\nrepoSlug: ${repoSlug}\n-----------\n\n`,
+ { encoding: "utf8" }
+ );
+
+ // console.log(
+ // `username: ${username}\npublicKey: ${publicKey}\ncommand: ${command}\nrepoSlug: ${repoSlug}\n`
+ // );
+
+ const res = await fetch(`http://localhost:1337/_ssh/auth`, {
+ method: "POST",
+ body: JSON.stringify({
+ command,
+ repoSlug,
+ username,
+ publicKey,
+ }),
+ });
+
+ if (res.ok === false) {
+ const text = await res.text();
+ fs.appendFileSync(
+ "/home/git/ssh_commands.log",
+ `${res.status}: ${res.statusText} - ${text}\n-----------\n\n`,
+ { encoding: "utf8" }
+ );
+ console.log("Forbidden access.");
+ process.exit(128);
+ return;
+ }
+
+ const json = await res.json();
+
+ console.log(JSON.stringify(json));
+
+ fs.appendFileSync(
+ "/home/git/ssh_commands.log",
+ `${JSON.stringify(json, null, 2)}\n-----------\n\n`,
+ { encoding: "utf8" }
+ );
+
+ if (json.success === false) {
+ console.log("Forbidden access.");
+ process.exit(128);
+ }
+
+ // success!
+ process.exit(0);
+}
+
+main(process.argv, process.env.SSH_ORIGINAL_COMMAND);
@@ -0,0 +1,15 @@
+-- CreateTable
+CREATE TABLE "UserSSHKey" (
+ "id" TEXT NOT NULL,
+ "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ "updatedAt" TIMESTAMP(3) NOT NULL,
+ "userId" TEXT NOT NULL,
+ "name" TEXT NOT NULL,
+ "key" TEXT NOT NULL,
+ "revoked" BOOLEAN NOT NULL DEFAULT false,
+
+ CONSTRAINT "UserSSHKey_pkey" PRIMARY KEY ("id")
+);
+
+-- AddForeignKey
+ALTER TABLE "UserSSHKey" ADD CONSTRAINT "UserSSHKey_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
@@ -11,12 +11,15 @@ datasource db {
model Organization {
id String @id @default(cuid())
- ownerId String
- slug String @unique
-
+
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
+ slug String @unique
+
+ owner User @relation("ManyOwnedOrganizationsToOneOwnerUser", fields: [ownerId], references: [id])
+ ownerId String
+
kind OrganizationKind @default(PERSONAL)
visibility ResourceVisibility @default(PRIVATE)
avatarUri String?
@@ -24,7 +27,6 @@ model Organization {
websiteUrl String?
memberships OrganizationMembership[] @relation("OneOrganizationMembershipToOneOrganization")
- owner User @relation("ManyOwnedOrganizationsToOneOwnerUser", fields: [ownerId], references: [id])
repositories Repository[] @relation("ManyRepositoriesToOneOrganization")
}
@@ -128,7 +130,6 @@ model Session {
model User {
id String @id @default(cuid())
-
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@ -139,12 +140,28 @@ model User {
displayName String?
avatarUri String?
+ sshKeys UserSSHKey[] @relation("ManyUserSSHKeyToOneUser")
+
organizations Organization[] @relation("ManyOwnedOrganizationsToOneOwnerUser")
organizationMemberships OrganizationMembership[] @relation("OneOrganizationMembershipToOneUser")
pullRequestsWhereAuthor PullRequest[] @relation("OnePullRequestToOneUser")
pullRequestCommentsWhereAuthor PullRequestComment[] @relation("OnePullRequestToOnePRCommenterUser")
}
+model UserSSHKey {
+ id String @id @default(cuid())
+ createdAt DateTime @default(now())
+ updatedAt DateTime @updatedAt
+
+ user User @relation("ManyUserSSHKeyToOneUser", fields: [userId], references: [id])
+ userId String
+
+ name String
+ key String
+
+ revoked Boolean @default(false)
+}
+
enum GlobalRole {
GUEST
CUSTOMER
@@ -22,9 +22,11 @@ services:
- db
ports:
- 1337:1337
+ - 22:22
volumes:
- ./data/gitfoss_repos:/var/lib/gitfoss/repos
- env_file: .env.local
+ - ./data/gitfoss_repos:/home/git/repos
+ env_file: .env.docker
# environment:
# - COOKIE_NAME=gitfoss_ssid
# - COOKIE_SECRET=gitfoss-cookie-secret
@@ -10,13 +10,13 @@
"postinstall": "patch-package",
"clean": "rm -rf dist/",
"generate": "run-s generate:prisma",
- "generate:prisma": "prisma generate",
+ "generate:prisma": "dotenv -e ./.env.local -- prisma generate",
"generate:prisma-data-proxy": "prisma generate --data-proxy",
"gitstamp": "git rev-parse HEAD > .gitstamp",
- "db:push": "prisma db push --preview-feature",
- "migrate:dev": "prisma migrate dev",
- "migrate:deploy": "prisma migrate deploy",
- "migrate:reset": "prisma migrate reset",
+ "db:push": "dotenv -e ./.env.local -- prisma db push --preview-feature",
+ "migrate:dev": "dotenv -e ./.env.local -- prisma migrate dev",
+ "migrate:deploy": "dotenv -e ./.env.local -- prisma migrate deploy",
+ "migrate:reset": "dotenv -e ./.env.local -- prisma migrate reset",
"bundle:islands": "NODE_ENV=production bundle-islands",
"build:ts": "NODE_ENV=production tsc",
"build": "run-s clean generate build:ts bundle:islands",
@@ -35,9 +35,11 @@
"@fastify/cookie": "6.0.0",
"@fastify/formbody": "6.0.0",
"@prisma/client": "^4.9.0",
+ "color": "^4.2.3",
"cross-fetch": "^3.1.5",
"cuid": "^2.1.8",
"diffparser": "^2.0.1",
+ "dotenv-cli": "^7.3.0",
"dotenv-flow": "^3.2.0",
"esbuild-plugin-prismjs": "^1.0.8",
"fastify": "^3.27.4",
@@ -63,6 +65,7 @@
},
"devDependencies": {
"@babel/core": "^7.0.0-0",
+ "@types/color": "^3.0.6",
"@types/cuid": "^2.0.1",
"@types/dotenv-flow": "^3.2.0",
"@types/fastify-static": "^2.2.1",
@@ -2,19 +2,7 @@
"name": "GitFOSS",
"short_name": "GitFOSS",
"start_url": "/",
- "icons": [
- {
- "src": "/public/assets/social-icon-192.png",
- "sizes": "192x192",
- "type": "image/png",
- "purpose": "any maskable"
- },
- {
- "src": "/public/assets/social-icon.png",
- "sizes": "512x512",
- "type": "image/png"
- }
- ],
+ "icons": [],
"display": "standalone",
"orientation": "portrait",
"theme_color": "#1B8F97",
@@ -8,6 +8,7 @@ import { PrismaClient } from "@prisma/client";
import type { AppSessionData, AppThemeScheme } from "../../app/types";
import type { CodeAnalysisServiceAPI } from "../../app/services/codeAnalysis/types";
import type { CryptoServiceAPI } from "../../app/services/crypto/types";
+import type { GitServerServiceAPI } from "../../services/gitServer/types";
declare module "@ethicdevs/fastify-custom-session" {
// from app types
@@ -20,6 +21,8 @@ declare module "fastify" {
codeAnalysisService: CodeAnalysisServiceAPI;
// from crypto plugin
cryptoService: CryptoServiceAPI;
+ // from server file
+ gitService: GitServerServiceAPI;
// from prisma plugin
prisma: PrismaClient;
}
@@ -33,6 +36,8 @@ declare module "fastify" {
// from crypto plugin
cryptoService: CryptoServiceAPI;
// from server file
+ gitService: GitServerServiceAPI;
+ // from server file
gitStamp: string;
// from react-monolith: request utility that maps a viewName to its routerPath
namedViewsPathMap: Record<string, string>;
@@ -926,6 +926,25 @@
"@types/connect" "*"
"@types/node" "*"
+"@types/color-convert@*":
+ version "2.0.3"
+ resolved "https://registry.yarnpkg.com/@types/color-convert/-/color-convert-2.0.3.tgz#e93f5c991eda87a945058b47044f5f0008b0dce9"
+ integrity sha512-2Q6wzrNiuEvYxVQqhh7sXM2mhIhvZR/Paq4FdsQkOMgWsCIkKvSGj8Le1/XalulrmgOzPMqNa0ix+ePY4hTrfg==
+ dependencies:
+ "@types/color-name" "*"
+
+"@types/color-name@*":
+ version "1.1.3"
+ resolved "https://registry.yarnpkg.com/@types/color-name/-/color-name-1.1.3.tgz#c488ac2e519c9795faa0d54e8156d54e66adc4e6"
+ integrity sha512-87W6MJCKZYDhLAx/J1ikW8niMvmGRyY+rpUxWpL1cO7F8Uu5CHuQoFv+R0/L5pgNdW4jTyda42kv60uwVIPjLw==
+
+"@types/color@^3.0.6":
+ version "3.0.6"
+ resolved "https://registry.yarnpkg.com/@types/color/-/color-3.0.6.tgz#29c27a99d4de2975e1676712679a0bd7f646a3fb"
+ integrity sha512-NMiNcZFRUAiUUCCf7zkAelY8eV3aKqfbzyFQlXpPIEeoNDbsEHGpb854V3gzTsGKYj830I5zPuOwU/TP5/cW6A==
+ dependencies:
+ "@types/color-convert" "*"
+
"@types/connect@*":
version "3.4.35"
resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.35.tgz#5fcf6ae445e4021d1fc2219a4873cc73a3bb2ad1"
@@ -1538,10 +1557,26 @@ color-name@1.1.3:
version "1.1.3"
resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25"
-color-name@~1.1.4:
+color-name@^1.0.0, color-name@~1.1.4:
version "1.1.4"
resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2"
+color-string@^1.9.0:
+ version "1.9.1"
+ resolved "https://registry.yarnpkg.com/color-string/-/color-string-1.9.1.tgz#4467f9146f036f855b764dfb5bf8582bf342c7a4"
+ integrity sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==
+ dependencies:
+ color-name "^1.0.0"
+ simple-swizzle "^0.2.2"
+
+color@^4.2.3:
+ version "4.2.3"
+ resolved "https://registry.yarnpkg.com/color/-/color-4.2.3.tgz#d781ecb5e57224ee43ea9627560107c0e0c6463a"
+ integrity sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==
+ dependencies:
+ color-convert "^2.0.1"
+ color-string "^1.9.0"
+
combined-stream@^1.0.8:
version "1.0.8"
resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f"
@@ -1761,12 +1796,32 @@ domexception@^2.0.1:
dependencies:
webidl-conversions "^5.0.0"
+dotenv-cli@^7.3.0:
+ version "7.3.0"
+ resolved "https://registry.yarnpkg.com/dotenv-cli/-/dotenv-cli-7.3.0.tgz#21e33e7944713001677658d68856063968edfbd2"
+ integrity sha512-314CA4TyK34YEJ6ntBf80eUY+t1XaFLyem1k9P0sX1gn30qThZ5qZr/ZwE318gEnzyYP9yj9HJk6SqwE0upkfw==
+ dependencies:
+ cross-spawn "^7.0.3"
+ dotenv "^16.3.0"
+ dotenv-expand "^10.0.0"
+ minimist "^1.2.6"
+
+dotenv-expand@^10.0.0:
+ version "10.0.0"
+ resolved "https://registry.yarnpkg.com/dotenv-expand/-/dotenv-expand-10.0.0.tgz#12605d00fb0af6d0a592e6558585784032e4ef37"
+ integrity sha512-GopVGCpVS1UKH75VKHGuQFqS1Gusej0z4FyQkPdwjil2gNIv+LNsqBlboOzpJFZKVT95GkCyWJbBSdFEFUWI2A==
+
dotenv-flow@^3.2.0:
version "3.2.0"
resolved "https://registry.yarnpkg.com/dotenv-flow/-/dotenv-flow-3.2.0.tgz#a5d79dd60ddb6843d457a4874aaf122cf659a8b7"
dependencies:
dotenv "^8.0.0"
+dotenv@^16.3.0:
+ version "16.3.1"
+ resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.3.1.tgz#369034de7d7e5b120972693352a3bf112172cc3e"
+ integrity sha512-IPzF4w4/Rd94bA9imS68tZBaYyBWSCE47V1RGuMrB94iyTOIEwRmVL2x/4An+6mETpLrKJ5hQkB8W4kFAadeIQ==
+
dotenv@^8.0.0:
version "8.6.0"
resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-8.6.0.tgz#061af664d19f7f4d8fc6e4ff9b584ce237adcb8b"
@@ -2670,6 +2725,11 @@ is-arrayish@^0.2.1:
version "0.2.1"
resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d"
+is-arrayish@^0.3.1:
+ version "0.3.2"
+ resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.3.2.tgz#4574a2ae56f7ab206896fb431eaeed066fdf8f03"
+ integrity sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==
+
is-bigint@^1.0.1:
version "1.0.4"
resolved "https://registry.yarnpkg.com/is-bigint/-/is-bigint-1.0.4.tgz#08147a1875bc2b32005d41ccd8291dffc6691df3"
@@ -4555,6 +4615,13 @@ signal-exit@^3.0.2, signal-exit@^3.0.3:
version "3.0.7"
resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9"
+simple-swizzle@^0.2.2:
+ version "0.2.2"
+ resolved "https://registry.yarnpkg.com/simple-swizzle/-/simple-swizzle-0.2.2.tgz#a4da6b635ffcccca33f70d17cb92592de95e557a"
+ integrity sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==
+ dependencies:
+ is-arrayish "^0.3.1"
+
sisteransi@^1.0.5:
version "1.0.5"
resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed"