feat: add github oauth

This commit is contained in:
Khairul Hidayat 2024-11-17 17:54:41 +07:00
parent c8e61ed4aa
commit a6cc450ea7
22 changed files with 419 additions and 74 deletions

View File

@ -3,3 +3,7 @@ DATABASE_URL=
# Generate a 32-byte (256-bit) key for AES-256 encryption using `openssl rand -hex 32` # Generate a 32-byte (256-bit) key for AES-256 encryption using `openssl rand -hex 32`
ENCRYPTION_KEY="" ENCRYPTION_KEY=""
# OAuth client
GITHUB_CLIENT_ID=
GITHUB_CLIENT_SECRET=

View File

@ -1,42 +0,0 @@
import { ExpoConfig, ConfigContext } from "expo/config";
export default ({ config }: ConfigContext): ExpoConfig => ({
...config,
name: "Vaulterm",
slug: "vaulterm",
version: "1.0.0",
orientation: "portrait",
icon: "./assets/images/icon.png",
scheme: "vaulterm",
userInterfaceStyle: "automatic",
newArchEnabled: true,
splash: {
image: "./assets/images/splash.png",
resizeMode: "contain",
backgroundColor: "#ffffff",
},
ios: {
supportsTablet: true,
},
android: {
package: "sh.rul.vaulterm",
adaptiveIcon: {
foregroundImage: "./assets/images/adaptive-icon.png",
backgroundColor: "#ffffff",
},
},
web: {
bundler: "metro",
output: "single",
favicon: "./assets/images/favicon.png",
},
plugins: ["expo-router"],
experiments: {
typedRoutes: true,
},
extra: {
eas: {
projectId: "3e0112c1-f0ed-423c-b5cf-95633f23f6dc",
},
},
});

41
frontend/app.json Normal file
View File

@ -0,0 +1,41 @@
{
"name": "Vaulterm",
"slug": "vaulterm",
"version": "1.0.0",
"orientation": "portrait",
"icon": "./assets/images/icon.png",
"scheme": "vaulterm",
"userInterfaceStyle": "automatic",
"newArchEnabled": true,
"splash": {
"image": "./assets/images/splash.png",
"resizeMode": "contain",
"backgroundColor": "#ffffff"
},
"ios": {
"supportsTablet": true
},
"android": {
"package": "sh.rul.vaulterm",
"adaptiveIcon": {
"foregroundImage": "./assets/images/adaptive-icon.png",
"backgroundColor": "#ffffff"
}
},
"web": {
"bundler": "metro",
"output": "single",
"favicon": "./assets/images/favicon.png"
},
"plugins": [
"expo-router"
],
"experiments": {
"typedRoutes": true
},
"extra": {
"eas": {
"projectId": "3e0112c1-f0ed-423c-b5cf-95633f23f6dc"
}
}
}

View File

@ -24,7 +24,7 @@ const Drawer = (props: DrawerContentComponentProps) => {
return ( return (
<View pt={insets.top} flex={1}> <View pt={insets.top} flex={1}>
<View py="$4" px="$2"> <View py="$4" px="$3">
<UserMenuButton /> <UserMenuButton />
</View> </View>

View File

@ -1,18 +1,11 @@
import React from "react"; import React from "react";
import { import { Button, ListItem, Separator, Text, useMedia, View } from "tamagui";
Avatar,
Button,
ListItem,
Separator,
Text,
useMedia,
View,
} from "tamagui";
import MenuButton from "../ui/menu-button"; import MenuButton from "../ui/menu-button";
import Icons from "../ui/icons"; import Icons from "../ui/icons";
import { logout, setTeam, useTeamId } from "@/stores/auth"; import { logout, setTeam, useTeamId } from "@/stores/auth";
import { useUser } from "@/hooks/useUser"; import { useUser } from "@/hooks/useUser";
import TeamForm, { teamFormModal } from "@/pages/team/components/team-form"; import TeamForm, { teamFormModal } from "@/pages/team/components/team-form";
import Avatar from "../ui/avatar";
const UserMenuButton = () => { const UserMenuButton = () => {
const user = useUser(); const user = useUser();
@ -31,16 +24,21 @@ const UserMenuButton = () => {
borderWidth={0} borderWidth={0}
justifyContent="flex-start" justifyContent="flex-start"
borderRadius="$10" borderRadius="$10"
py={0} p={0}
px="$2"
gap="$1" gap="$1"
> >
<Avatar circular size="$3"> <Avatar size="$4" src={user?.image} />
<Avatar.Fallback bg="$blue4" />
</Avatar>
<View flex={1}> <View flex={1}>
<Text textAlign='left' numberOfLines={1}>{user?.name}</Text> <Text textAlign="left" numberOfLines={1}>
<Text textAlign='left' numberOfLines={1} fontWeight="600" mt="$1.5"> {user?.name}
</Text>
<Text
textAlign="left"
numberOfLines={1}
fontWeight="600"
mt="$1.5"
>
{team ? `${team.icon} ${team.name}` : "Personal"} {team ? `${team.icon} ${team.name}` : "Personal"}
</Text> </Text>
</View> </View>

View File

@ -0,0 +1,25 @@
import React from "react";
import { GetProps, Avatar as BaseAvatar } from "tamagui";
import Icons from "./icons";
type AvatarProps = GetProps<typeof BaseAvatar> & {
src?: string;
$image?: GetProps<typeof BaseAvatar.Image>;
};
const Avatar = ({ src, $image, ...props }: AvatarProps) => {
return (
<BaseAvatar circular {...props}>
{src ? <BaseAvatar.Image src={src} {...$image} /> : null}
<BaseAvatar.Fallback
bg="$blue4"
alignItems="center"
justifyContent="center"
>
<Icons name="account" size={16} />
</BaseAvatar.Fallback>
</BaseAvatar>
);
};
export default Avatar;

View File

@ -0,0 +1,19 @@
import api from "@/lib/api";
import { useQuery } from "@tanstack/react-query";
export const useServerConfig = (configName?: string | string[]) => {
return useQuery({
queryKey: ["server/config", configName],
queryFn: () => {
const keys = Array.isArray(configName)
? configName.join(",")
: configName;
return api("/server/config", { params: { keys } });
},
select: (data) => {
return typeof configName === "string"
? (data[configName] as string)
: data;
},
});
};

View File

@ -31,8 +31,10 @@
"@xterm/addon-fit": "^0.10.0", "@xterm/addon-fit": "^0.10.0",
"@xterm/xterm": "^5.5.0", "@xterm/xterm": "^5.5.0",
"expo": "~52.0.6", "expo": "~52.0.6",
"expo-auth-session": "~6.0.0",
"expo-blur": "~14.0.1", "expo-blur": "~14.0.1",
"expo-constants": "~17.0.3", "expo-constants": "~17.0.3",
"expo-crypto": "~14.0.1",
"expo-font": "~13.0.0", "expo-font": "~13.0.0",
"expo-haptics": "~14.0.0", "expo-haptics": "~14.0.0",
"expo-linking": "~7.0.2", "expo-linking": "~7.0.2",

View File

@ -0,0 +1,42 @@
import React, { useEffect, useMemo } from "react";
import { makeRedirectUri, useAuthRequest } from "expo-auth-session";
import appConfig from "@/app.json";
import { Button } from "tamagui";
import { useOAuthCallback } from "../hooks";
import { useServerConfig } from "@/hooks/useServerConfig";
const LoginGithubButton = () => {
const { data: clientId } = useServerConfig("github_client_id");
const discovery = useMemo(() => {
return {
authorizationEndpoint: "https://github.com/login/oauth/authorize",
tokenEndpoint: "https://github.com/login/oauth/access_token",
revocationEndpoint: `https://github.com/settings/connections/applications/${clientId}`,
};
}, [clientId]);
const oauth = useOAuthCallback("github");
const [request, response, promptAsync] = useAuthRequest(
{
clientId: clientId,
scopes: ["identity"],
redirectUri: makeRedirectUri({ scheme: appConfig.scheme }),
},
discovery
);
useEffect(() => {
if (response?.type === "success") {
const { code } = response.params;
oauth.mutate(code);
}
}, [response]);
return (
<Button disabled={!request} onPress={() => promptAsync()}>
Login with Github
</Button>
);
};
export default LoginGithubButton;

View File

@ -42,3 +42,20 @@ export const useRegisterMutation = () => {
}, },
}); });
}; };
export const useOAuthCallback = (type: string) => {
return useMutation({
mutationFn: async (code: string) => {
const res = await api(`/auth/oauth/${type}/callback?code=${code}`);
const { data } = loginResultSchema.safeParse(res);
if (!data) {
throw new Error("Invalid response!");
}
return data;
},
onSuccess(data) {
authStore.setState({ token: data.sessionId });
router.replace("/");
},
});
};

View File

@ -6,17 +6,21 @@ import { useZForm } from "@/hooks/useZForm";
import { Link, router, Stack } from "expo-router"; import { Link, router, Stack } from "expo-router";
import Button from "@/components/ui/button"; import Button from "@/components/ui/button";
import ThemeSwitcher from "@/components/containers/theme-switcher"; import ThemeSwitcher from "@/components/containers/theme-switcher";
import { useMutation } from "@tanstack/react-query"; import * as WebBrowser from "expo-web-browser";
import { z } from "zod";
import { ErrorAlert } from "@/components/ui/alert"; import { ErrorAlert } from "@/components/ui/alert";
import { loginSchema } from "./schema"; import { loginSchema } from "./schema";
import Icons from "@/components/ui/icons"; import Icons from "@/components/ui/icons";
import tamaguiConfig from "@/tamagui.config"; import tamaguiConfig from "@/tamagui.config";
import { useLoginMutation } from "./hooks"; import { useLoginMutation } from "./hooks";
import LoginGithubButton from "./components/login-github";
import { useServerConfig } from "@/hooks/useServerConfig";
WebBrowser.maybeCompleteAuthSession();
export default function LoginPage() { export default function LoginPage() {
const form = useZForm(loginSchema); const form = useZForm(loginSchema);
const login = useLoginMutation(); const login = useLoginMutation();
const { data: oauthList } = useServerConfig("oauth");
const onSubmit = form.handleSubmit((values) => { const onSubmit = form.handleSubmit((values) => {
login.mutate(values); login.mutate(values);
@ -98,6 +102,16 @@ export default function LoginPage() {
</Link> </Link>
</XStack> </XStack>
{oauthList?.length > 0 && (
<>
<Text textAlign="center" fontSize="$3">
or
</Text>
{oauthList.includes("github") && <LoginGithubButton />}
</>
)}
<Separator w="100%" /> <Separator w="100%" />
<Button <Button

View File

@ -1,5 +1,5 @@
import React, { useMemo, useState } from "react"; import React, { useMemo, useState } from "react";
import { Avatar, Button, ListItem, View, YGroup } from "tamagui"; import { Button, ListItem, View, YGroup } from "tamagui";
import MenuButton from "@/components/ui/menu-button"; import MenuButton from "@/components/ui/menu-button";
import Icons from "@/components/ui/icons"; import Icons from "@/components/ui/icons";
import SearchInput from "@/components/ui/search-input"; import SearchInput from "@/components/ui/search-input";
@ -7,6 +7,7 @@ import { useTeamId } from "@/stores/auth";
import { changeRoleModal } from "./change-role-form"; import { changeRoleModal } from "./change-role-form";
import { useRemoveMemberMutation } from "../hooks/query"; import { useRemoveMemberMutation } from "../hooks/query";
import { showDialog } from "@/hooks/useDialog"; import { showDialog } from "@/hooks/useDialog";
import Avatar from "@/components/ui/avatar";
type Props = { type Props = {
members?: any[]; members?: any[];
@ -59,11 +60,7 @@ const MemberList = ({ members, allowWrite }: Props) => {
title={member.user?.name} title={member.user?.name}
subTitle={member.role} subTitle={member.role}
pr="$2" pr="$2"
icon={ icon={<Avatar src={member.user?.image} size="$4" />}
<Avatar size="$3" circular>
<Avatar.Fallback bg="$blue5" />
</Avatar>
}
iconAfter={ iconAfter={
allowWrite ? ( allowWrite ? (
<MemberActionButton <MemberActionButton

View File

@ -49,12 +49,18 @@ importers:
expo: expo:
specifier: ~52.0.6 specifier: ~52.0.6
version: 52.0.6(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@expo/metro-runtime@4.0.0(react-native@0.76.1(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.3.12)(react@18.3.1)))(react-native-webview@13.12.2(react-native@0.76.1(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.3.12)(react@18.3.1))(react@18.3.1))(react-native@0.76.1(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.3.12)(react@18.3.1))(react@18.3.1) version: 52.0.6(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@expo/metro-runtime@4.0.0(react-native@0.76.1(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.3.12)(react@18.3.1)))(react-native-webview@13.12.2(react-native@0.76.1(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.3.12)(react@18.3.1))(react@18.3.1))(react-native@0.76.1(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.3.12)(react@18.3.1))(react@18.3.1)
expo-auth-session:
specifier: ~6.0.0
version: 6.0.0(expo@52.0.6(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@expo/metro-runtime@4.0.0(react-native@0.76.1(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.3.12)(react@18.3.1)))(react-native-webview@13.12.2(react-native@0.76.1(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.3.12)(react@18.3.1))(react@18.3.1))(react-native@0.76.1(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.3.12)(react@18.3.1))(react@18.3.1))(react-native@0.76.1(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.3.12)(react@18.3.1))(react@18.3.1)
expo-blur: expo-blur:
specifier: ~14.0.1 specifier: ~14.0.1
version: 14.0.1(expo@52.0.6(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@expo/metro-runtime@4.0.0(react-native@0.76.1(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.3.12)(react@18.3.1)))(react-native-webview@13.12.2(react-native@0.76.1(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.3.12)(react@18.3.1))(react@18.3.1))(react-native@0.76.1(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.3.12)(react@18.3.1))(react@18.3.1))(react-native@0.76.1(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.3.12)(react@18.3.1))(react@18.3.1) version: 14.0.1(expo@52.0.6(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@expo/metro-runtime@4.0.0(react-native@0.76.1(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.3.12)(react@18.3.1)))(react-native-webview@13.12.2(react-native@0.76.1(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.3.12)(react@18.3.1))(react@18.3.1))(react-native@0.76.1(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.3.12)(react@18.3.1))(react@18.3.1))(react-native@0.76.1(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.3.12)(react@18.3.1))(react@18.3.1)
expo-constants: expo-constants:
specifier: ~17.0.3 specifier: ~17.0.3
version: 17.0.3(expo@52.0.6(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@expo/metro-runtime@4.0.0(react-native@0.76.1(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.3.12)(react@18.3.1)))(react-native-webview@13.12.2(react-native@0.76.1(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.3.12)(react@18.3.1))(react@18.3.1))(react-native@0.76.1(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.3.12)(react@18.3.1))(react@18.3.1))(react-native@0.76.1(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.3.12)(react@18.3.1)) version: 17.0.3(expo@52.0.6(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@expo/metro-runtime@4.0.0(react-native@0.76.1(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.3.12)(react@18.3.1)))(react-native-webview@13.12.2(react-native@0.76.1(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.3.12)(react@18.3.1))(react@18.3.1))(react-native@0.76.1(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.3.12)(react@18.3.1))(react@18.3.1))(react-native@0.76.1(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.3.12)(react@18.3.1))
expo-crypto:
specifier: ~14.0.1
version: 14.0.1(expo@52.0.6(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@expo/metro-runtime@4.0.0(react-native@0.76.1(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.3.12)(react@18.3.1)))(react-native-webview@13.12.2(react-native@0.76.1(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.3.12)(react@18.3.1))(react@18.3.1))(react-native@0.76.1(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.3.12)(react@18.3.1))(react@18.3.1))
expo-font: expo-font:
specifier: ~13.0.0 specifier: ~13.0.0
version: 13.0.1(expo@52.0.6(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@expo/metro-runtime@4.0.0(react-native@0.76.1(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.3.12)(react@18.3.1)))(react-native-webview@13.12.2(react-native@0.76.1(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.3.12)(react@18.3.1))(react@18.3.1))(react-native@0.76.1(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.3.12)(react@18.3.1))(react@18.3.1))(react@18.3.1) version: 13.0.1(expo@52.0.6(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@expo/metro-runtime@4.0.0(react-native@0.76.1(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.3.12)(react@18.3.1)))(react-native-webview@13.12.2(react-native@0.76.1(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.3.12)(react@18.3.1))(react@18.3.1))(react-native@0.76.1(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.3.12)(react@18.3.1))(react@18.3.1))(react@18.3.1)
@ -3159,6 +3165,11 @@ packages:
resolution: {integrity: sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==} resolution: {integrity: sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==}
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
expo-application@6.0.1:
resolution: {integrity: sha512-w+1quSmKp8SYKT+GAFHSN5c6u+PqoVRIfpsLyRQrQdOnBA9dA8Hw6JT9sHNFmA30A2v1b/sdYZE3qKuRJFNSWQ==}
peerDependencies:
expo: '*'
expo-asset@11.0.1: expo-asset@11.0.1:
resolution: {integrity: sha512-WatvD7JVC89EsllXFYcS/rji3ajVzE2B/USo0TqedsETixwyVCQfrrvCdCPQyuKghrxVNEj8bQ/Qbea/RZLYjg==} resolution: {integrity: sha512-WatvD7JVC89EsllXFYcS/rji3ajVzE2B/USo0TqedsETixwyVCQfrrvCdCPQyuKghrxVNEj8bQ/Qbea/RZLYjg==}
peerDependencies: peerDependencies:
@ -3166,6 +3177,12 @@ packages:
react: '*' react: '*'
react-native: '*' react-native: '*'
expo-auth-session@6.0.0:
resolution: {integrity: sha512-t40IvmUnWPdSFTr/d3FxDo3qbHdt6hPoRApZ9KH8/UoTjkdoSKnxi6W0/svpISDPMi25gB0lNYwy72YUisl1Yw==}
peerDependencies:
react: '*'
react-native: '*'
expo-blur@14.0.1: expo-blur@14.0.1:
resolution: {integrity: sha512-3Q6jFBLbY8n2vwk28ycUC+eIlVhnlqwkXUKk/Lfaj+SGV3AZMQyrixe7OYwJdUfwqETBrnYYMB6uNrJzOSbG+g==} resolution: {integrity: sha512-3Q6jFBLbY8n2vwk28ycUC+eIlVhnlqwkXUKk/Lfaj+SGV3AZMQyrixe7OYwJdUfwqETBrnYYMB6uNrJzOSbG+g==}
peerDependencies: peerDependencies:
@ -3179,6 +3196,11 @@ packages:
expo: '*' expo: '*'
react-native: '*' react-native: '*'
expo-crypto@14.0.1:
resolution: {integrity: sha512-/gGpD9UAz8fgZtU08cwwqeQElkFmMy2Hc8lLa9laSjD3YN0XM07zDJyJ+CC1VhQ63G8WpUnq1IHSmaPbbLp+oQ==}
peerDependencies:
expo: '*'
expo-file-system@18.0.3: expo-file-system@18.0.3:
resolution: {integrity: sha512-HKe0dGW3FWYFi1F3THVnTRueTG7j0onmEpUJKRB4UbjeHD2723cn/EutcG216wvrJeebe8w3+00F8Z4xk+9Jrw==} resolution: {integrity: sha512-HKe0dGW3FWYFi1F3THVnTRueTG7j0onmEpUJKRB4UbjeHD2723cn/EutcG216wvrJeebe8w3+00F8Z4xk+9Jrw==}
peerDependencies: peerDependencies:
@ -10010,6 +10032,10 @@ snapshots:
jest-message-util: 29.7.0 jest-message-util: 29.7.0
jest-util: 29.7.0 jest-util: 29.7.0
expo-application@6.0.1(expo@52.0.6(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@expo/metro-runtime@4.0.0(react-native@0.76.1(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.3.12)(react@18.3.1)))(react-native-webview@13.12.2(react-native@0.76.1(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.3.12)(react@18.3.1))(react@18.3.1))(react-native@0.76.1(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.3.12)(react@18.3.1))(react@18.3.1)):
dependencies:
expo: 52.0.6(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@expo/metro-runtime@4.0.0(react-native@0.76.1(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.3.12)(react@18.3.1)))(react-native-webview@13.12.2(react-native@0.76.1(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.3.12)(react@18.3.1))(react@18.3.1))(react-native@0.76.1(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.3.12)(react@18.3.1))(react@18.3.1)
expo-asset@11.0.1(expo@52.0.6(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@expo/metro-runtime@4.0.0(react-native@0.76.1(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.3.12)(react@18.3.1)))(react-native-webview@13.12.2(react-native@0.76.1(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.3.12)(react@18.3.1))(react@18.3.1))(react-native@0.76.1(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.3.12)(react@18.3.1))(react@18.3.1))(react-native@0.76.1(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.3.12)(react@18.3.1))(react@18.3.1): expo-asset@11.0.1(expo@52.0.6(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@expo/metro-runtime@4.0.0(react-native@0.76.1(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.3.12)(react@18.3.1)))(react-native-webview@13.12.2(react-native@0.76.1(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.3.12)(react@18.3.1))(react@18.3.1))(react-native@0.76.1(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.3.12)(react@18.3.1))(react@18.3.1))(react-native@0.76.1(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.3.12)(react@18.3.1))(react@18.3.1):
dependencies: dependencies:
'@expo/image-utils': 0.6.3 '@expo/image-utils': 0.6.3
@ -10022,6 +10048,20 @@ snapshots:
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
expo-auth-session@6.0.0(expo@52.0.6(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@expo/metro-runtime@4.0.0(react-native@0.76.1(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.3.12)(react@18.3.1)))(react-native-webview@13.12.2(react-native@0.76.1(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.3.12)(react@18.3.1))(react@18.3.1))(react-native@0.76.1(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.3.12)(react@18.3.1))(react@18.3.1))(react-native@0.76.1(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.3.12)(react@18.3.1))(react@18.3.1):
dependencies:
expo-application: 6.0.1(expo@52.0.6(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@expo/metro-runtime@4.0.0(react-native@0.76.1(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.3.12)(react@18.3.1)))(react-native-webview@13.12.2(react-native@0.76.1(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.3.12)(react@18.3.1))(react@18.3.1))(react-native@0.76.1(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.3.12)(react@18.3.1))(react@18.3.1))
expo-constants: 17.0.3(expo@52.0.6(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@expo/metro-runtime@4.0.0(react-native@0.76.1(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.3.12)(react@18.3.1)))(react-native-webview@13.12.2(react-native@0.76.1(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.3.12)(react@18.3.1))(react@18.3.1))(react-native@0.76.1(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.3.12)(react@18.3.1))(react@18.3.1))(react-native@0.76.1(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.3.12)(react@18.3.1))
expo-crypto: 14.0.1(expo@52.0.6(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@expo/metro-runtime@4.0.0(react-native@0.76.1(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.3.12)(react@18.3.1)))(react-native-webview@13.12.2(react-native@0.76.1(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.3.12)(react@18.3.1))(react@18.3.1))(react-native@0.76.1(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.3.12)(react@18.3.1))(react@18.3.1))
expo-linking: 7.0.2(expo@52.0.6(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@expo/metro-runtime@4.0.0(react-native@0.76.1(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.3.12)(react@18.3.1)))(react-native-webview@13.12.2(react-native@0.76.1(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.3.12)(react@18.3.1))(react@18.3.1))(react-native@0.76.1(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.3.12)(react@18.3.1))(react@18.3.1))(react-native@0.76.1(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.3.12)(react@18.3.1))(react@18.3.1)
expo-web-browser: 14.0.1(expo@52.0.6(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@expo/metro-runtime@4.0.0(react-native@0.76.1(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.3.12)(react@18.3.1)))(react-native-webview@13.12.2(react-native@0.76.1(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.3.12)(react@18.3.1))(react@18.3.1))(react-native@0.76.1(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.3.12)(react@18.3.1))(react@18.3.1))(react-native@0.76.1(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.3.12)(react@18.3.1))
invariant: 2.2.4
react: 18.3.1
react-native: 0.76.1(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.3.12)(react@18.3.1)
transitivePeerDependencies:
- expo
- supports-color
expo-blur@14.0.1(expo@52.0.6(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@expo/metro-runtime@4.0.0(react-native@0.76.1(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.3.12)(react@18.3.1)))(react-native-webview@13.12.2(react-native@0.76.1(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.3.12)(react@18.3.1))(react@18.3.1))(react-native@0.76.1(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.3.12)(react@18.3.1))(react@18.3.1))(react-native@0.76.1(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.3.12)(react@18.3.1))(react@18.3.1): expo-blur@14.0.1(expo@52.0.6(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@expo/metro-runtime@4.0.0(react-native@0.76.1(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.3.12)(react@18.3.1)))(react-native-webview@13.12.2(react-native@0.76.1(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.3.12)(react@18.3.1))(react@18.3.1))(react-native@0.76.1(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.3.12)(react@18.3.1))(react@18.3.1))(react-native@0.76.1(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.3.12)(react@18.3.1))(react@18.3.1):
dependencies: dependencies:
expo: 52.0.6(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@expo/metro-runtime@4.0.0(react-native@0.76.1(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.3.12)(react@18.3.1)))(react-native-webview@13.12.2(react-native@0.76.1(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.3.12)(react@18.3.1))(react@18.3.1))(react-native@0.76.1(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.3.12)(react@18.3.1))(react@18.3.1) expo: 52.0.6(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@expo/metro-runtime@4.0.0(react-native@0.76.1(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.3.12)(react@18.3.1)))(react-native-webview@13.12.2(react-native@0.76.1(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.3.12)(react@18.3.1))(react@18.3.1))(react-native@0.76.1(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.3.12)(react@18.3.1))(react@18.3.1)
@ -10037,6 +10077,11 @@ snapshots:
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
expo-crypto@14.0.1(expo@52.0.6(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@expo/metro-runtime@4.0.0(react-native@0.76.1(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.3.12)(react@18.3.1)))(react-native-webview@13.12.2(react-native@0.76.1(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.3.12)(react@18.3.1))(react@18.3.1))(react-native@0.76.1(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.3.12)(react@18.3.1))(react@18.3.1)):
dependencies:
base64-js: 1.5.1
expo: 52.0.6(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@expo/metro-runtime@4.0.0(react-native@0.76.1(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.3.12)(react@18.3.1)))(react-native-webview@13.12.2(react-native@0.76.1(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.3.12)(react@18.3.1))(react@18.3.1))(react-native@0.76.1(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.3.12)(react@18.3.1))(react@18.3.1)
expo-file-system@18.0.3(expo@52.0.6(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@expo/metro-runtime@4.0.0(react-native@0.76.1(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.3.12)(react@18.3.1)))(react-native-webview@13.12.2(react-native@0.76.1(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.3.12)(react@18.3.1))(react@18.3.1))(react-native@0.76.1(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.3.12)(react@18.3.1))(react@18.3.1))(react-native@0.76.1(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.3.12)(react@18.3.1)): expo-file-system@18.0.3(expo@52.0.6(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@expo/metro-runtime@4.0.0(react-native@0.76.1(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.3.12)(react@18.3.1)))(react-native-webview@13.12.2(react-native@0.76.1(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.3.12)(react@18.3.1))(react@18.3.1))(react-native@0.76.1(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.3.12)(react@18.3.1))(react@18.3.1))(react-native@0.76.1(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.3.12)(react@18.3.1)):
dependencies: dependencies:
expo: 52.0.6(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@expo/metro-runtime@4.0.0(react-native@0.76.1(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.3.12)(react@18.3.1)))(react-native-webview@13.12.2(react-native@0.76.1(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.3.12)(react@18.3.1))(react@18.3.1))(react-native@0.76.1(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.3.12)(react@18.3.1))(react@18.3.1) expo: 52.0.6(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@expo/metro-runtime@4.0.0(react-native@0.76.1(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.3.12)(react@18.3.1)))(react-native-webview@13.12.2(react-native@0.76.1(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.3.12)(react@18.3.1))(react@18.3.1))(react-native@0.76.1(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.3.12)(react@18.3.1))(react@18.3.1)

1
go.mod
View File

@ -12,6 +12,7 @@ require (
github.com/stretchr/testify v1.9.0 github.com/stretchr/testify v1.9.0
github.com/wailsapp/wails/v2 v2.9.2 github.com/wailsapp/wails/v2 v2.9.2
golang.org/x/crypto v0.28.0 golang.org/x/crypto v0.28.0
golang.org/x/oauth2 v0.24.0
gorm.io/datatypes v1.2.4 gorm.io/datatypes v1.2.4
gorm.io/driver/postgres v1.5.9 gorm.io/driver/postgres v1.5.9
gorm.io/driver/sqlite v1.5.6 gorm.io/driver/sqlite v1.5.6

4
go.sum
View File

@ -25,6 +25,8 @@ github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 h1:au07oEsX2xN0kt
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
github.com/golang-sql/sqlexp v0.1.0 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei6A= github.com/golang-sql/sqlexp v0.1.0 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei6A=
github.com/golang-sql/sqlexp v0.1.0/go.mod h1:J4ad9Vo8ZCWQ2GMrC4UCQy1JpCbwU9m3EOqtpKwwwHI= github.com/golang-sql/sqlexp v0.1.0/go.mod h1:J4ad9Vo8ZCWQ2GMrC4UCQy1JpCbwU9m3EOqtpKwwwHI=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
@ -126,6 +128,8 @@ golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1/go.mod h1:V1LtkGg67GoY2N1AnL
golang.org/x/net v0.0.0-20210505024714-0287a6fb4125/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210505024714-0287a6fb4125/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ=
golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE=
golang.org/x/oauth2 v0.24.0 h1:KTBBxWqUa0ykRPLtV69rRto9TLXcqYkeswu48x/gvNE=
golang.org/x/oauth2 v0.24.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=

View File

@ -8,6 +8,7 @@ import (
"github.com/gofiber/fiber/v2/middleware/cors" "github.com/gofiber/fiber/v2/middleware/cors"
"github.com/joho/godotenv" "github.com/joho/godotenv"
"rul.sh/vaulterm/server/app/auth" "rul.sh/vaulterm/server/app/auth"
"rul.sh/vaulterm/server/app/server"
"rul.sh/vaulterm/server/db" "rul.sh/vaulterm/server/db"
"rul.sh/vaulterm/server/middleware" "rul.sh/vaulterm/server/middleware"
"rul.sh/vaulterm/server/utils" "rul.sh/vaulterm/server/utils"
@ -31,12 +32,7 @@ func NewApp() *fiber.App {
app.Use(cors.New()) app.Use(cors.New())
// Server info // Server info
app.Get("/server", func(c *fiber.Ctx) error { server.Router(app)
return c.JSON(&fiber.Map{
"name": "Vaulterm",
"version": "0.0.1",
})
})
// Health check // Health check
app.Get("/health-check", func(c *fiber.Ctx) error { app.Get("/health-check", func(c *fiber.Ctx) error {

View File

@ -0,0 +1,124 @@
package auth
import (
"encoding/json"
"errors"
"fmt"
"io"
"os"
"strconv"
"github.com/gofiber/fiber/v2"
"golang.org/x/oauth2"
"golang.org/x/oauth2/github"
"gorm.io/gorm"
"rul.sh/vaulterm/server/models"
"rul.sh/vaulterm/server/utils"
)
var config *oauth2.Config
func getGithubConfig() *oauth2.Config {
if config != nil {
return config
}
config = &oauth2.Config{
ClientID: os.Getenv("GITHUB_CLIENT_ID"),
ClientSecret: os.Getenv("GITHUB_CLIENT_SECRET"),
Endpoint: github.Endpoint,
RedirectURL: "http://localhost:3000/auth/oauth/github/callback",
Scopes: []string{"read:user"},
}
return config
}
func githubRedir(c *fiber.Ctx) error {
// Redirect to GitHub login page
url := getGithubConfig().AuthCodeURL("state", oauth2.AccessTypeOffline)
return c.Redirect(url)
}
func githubCallback(c *fiber.Ctx) error {
code := c.Query("code")
if code == "" {
return c.Status(fiber.StatusBadRequest).SendString("Missing code")
}
// Exchange code for a token
cfg := getGithubConfig()
token, err := cfg.Exchange(c.Context(), code)
if err != nil {
return c.Status(fiber.StatusInternalServerError).SendString("Failed to exchange token")
}
// Retrieve user info
client := cfg.Client(c.Context(), token)
resp, err := client.Get("https://api.github.com/user")
if err != nil {
return c.Status(fiber.StatusInternalServerError).SendString("Failed to get user info")
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
if resp.StatusCode != 200 {
return c.Status(fiber.StatusInternalServerError).
SendString(fmt.Sprintf("GitHub API error: %s", string(body)))
}
// Parse user info
var user struct {
Login string `json:"login"`
ID int `json:"id"`
Name string `json:"name"`
AvatarURL string `json:"avatar_url"`
Email string `json:"email"`
}
if err := json.Unmarshal(body, &user); err != nil {
return c.Status(fiber.StatusInternalServerError).SendString("Failed to parse user info")
}
repo := NewRepository()
accountId := strconv.Itoa(user.ID)
userAccount, err := repo.FindUserAccount("github", accountId)
// Register the user if the account not yet registered
if errors.Is(err, gorm.ErrRecordNotFound) {
acc := models.UserAccount{
Type: "github",
AccountID: accountId,
Username: user.Login,
Email: user.Email,
}
user := models.User{
Name: user.Name,
Role: models.UserRoleUser,
Image: user.AvatarURL,
Accounts: []*models.UserAccount{&acc},
}
sessionId, err := repo.CreateUser(&user)
if err != nil {
return utils.ResponseError(c, err, 500)
}
return c.JSON(fiber.Map{
"user": user,
"sessionId": sessionId,
})
}
if err != nil {
return utils.ResponseError(c, err, 500)
}
sessionId, err := repo.CreateUserSession(&userAccount.User)
if err != nil {
return utils.ResponseError(c, err, 500)
}
return c.JSON(fiber.Map{
"user": userAccount.User,
"sessionId": sessionId,
})
}

View File

@ -23,6 +23,12 @@ func (r *Auth) FindUser(username string, email string) (*models.User, error) {
return &user, ret.Error return &user, ret.Error
} }
func (r *Auth) FindUserAccount(accountType string, accountId string) (*models.UserAccount, error) {
var user models.UserAccount
ret := r.db.Where("type = ? AND account_id = ?", accountType, accountId).Joins("User").First(&user)
return &user, ret.Error
}
func (r *Auth) CreateUserSession(user *models.User) (string, error) { func (r *Auth) CreateUserSession(user *models.User) (string, error) {
sessionId, err := lib.GenerateSessionID(20) sessionId, err := lib.GenerateSessionID(20)
if err != nil { if err != nil {

View File

@ -15,6 +15,10 @@ func Router(app *fiber.App) {
router.Get("/user", middleware.Protected(), getUser) router.Get("/user", middleware.Protected(), getUser)
router.Post("/register", register) router.Post("/register", register)
router.Post("/logout", middleware.Protected(), logout) router.Post("/logout", middleware.Protected(), logout)
oauth := router.Group("/oauth")
oauth.Get("/github", githubRedir)
oauth.Get("/github/callback", githubCallback)
} }
func login(c *fiber.Ctx) error { func login(c *fiber.Ctx) error {

View File

@ -0,0 +1,29 @@
package server
import (
"os"
"github.com/gofiber/fiber/v2"
)
func Router(app fiber.Router) {
router := app.Group("/server")
router.Get("/", getServerInfo)
router.Get("/config", getConfig)
}
func getServerInfo(c *fiber.Ctx) error {
return c.JSON(&fiber.Map{
"name": "Vaulterm",
"version": "0.0.1",
})
}
func getConfig(c *fiber.Ctx) error {
config := fiber.Map{
"oauth": "github",
"github_client_id": os.Getenv("GITHUB_CLIENT_ID"),
}
return c.JSON(config)
}

View File

@ -7,6 +7,7 @@ import (
var Models = []interface{}{ var Models = []interface{}{
&models.User{}, &models.User{},
&models.UserSession{}, &models.UserSession{},
&models.UserAccount{},
&models.Keychain{}, &models.Keychain{},
&models.Host{}, &models.Host{},
&models.Team{}, &models.Team{},

View File

@ -5,6 +5,8 @@ import "slices"
const ( const (
UserRoleUser = "user" UserRoleUser = "user"
UserRoleAdmin = "admin" UserRoleAdmin = "admin"
UserAccountTypeGithub = "github"
) )
type User struct { type User struct {
@ -15,8 +17,10 @@ type User struct {
Password string `json:"-"` Password string `json:"-"`
Email string `json:"email" gorm:"unique"` Email string `json:"email" gorm:"unique"`
Role string `json:"role" gorm:"default:user;not null;index:users_role_idx;type:varchar(8)"` Role string `json:"role" gorm:"default:user;not null;index:users_role_idx;type:varchar(8)"`
Image string `json:"image" gorm:"type:varchar(255)"`
Teams []*TeamMembers `json:"teams" gorm:"foreignKey:UserID"` Teams []*TeamMembers `json:"teams" gorm:"foreignKey:UserID"`
Accounts []*UserAccount `json:"accounts" gorm:"foreignKey:UserID"`
Timestamps Timestamps
SoftDeletes SoftDeletes
@ -31,6 +35,20 @@ type UserSession struct {
SoftDeletes SoftDeletes
} }
type UserAccount struct {
Model
UserID string `json:"userId" gorm:"type:varchar(26)"`
User User `json:"user" gorm:"foreignKey:UserID"`
Type string `json:"type" gorm:"type:varchar(16);index:user_accounts_type_idx"`
AccountID string `json:"accountId" gorm:"type:varchar(64);index:user_accounts_accountid_idx"`
Username string `json:"username" gorm:"type:varchar(64);index:user_accounts_username_idx"`
Email string `json:"email" gorm:"type:varchar(64);index:user_accounts_email_idx"`
Timestamps
}
func (u *User) IsAdmin() bool { func (u *User) IsAdmin() bool {
return u.Role == UserRoleAdmin return u.Role == UserRoleAdmin
} }