mirror of
https://github.com/khairul169/vaulterm.git
synced 2025-04-28 08:39:37 +07:00
feat: add github oauth
This commit is contained in:
parent
c8e61ed4aa
commit
a6cc450ea7
@ -3,3 +3,7 @@ DATABASE_URL=
|
||||
|
||||
# Generate a 32-byte (256-bit) key for AES-256 encryption using `openssl rand -hex 32`
|
||||
ENCRYPTION_KEY=""
|
||||
|
||||
# OAuth client
|
||||
GITHUB_CLIENT_ID=
|
||||
GITHUB_CLIENT_SECRET=
|
||||
|
@ -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
41
frontend/app.json
Normal 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"
|
||||
}
|
||||
}
|
||||
}
|
@ -24,7 +24,7 @@ const Drawer = (props: DrawerContentComponentProps) => {
|
||||
|
||||
return (
|
||||
<View pt={insets.top} flex={1}>
|
||||
<View py="$4" px="$2">
|
||||
<View py="$4" px="$3">
|
||||
<UserMenuButton />
|
||||
</View>
|
||||
|
||||
|
@ -1,18 +1,11 @@
|
||||
import React from "react";
|
||||
import {
|
||||
Avatar,
|
||||
Button,
|
||||
ListItem,
|
||||
Separator,
|
||||
Text,
|
||||
useMedia,
|
||||
View,
|
||||
} from "tamagui";
|
||||
import { Button, ListItem, Separator, Text, useMedia, View } from "tamagui";
|
||||
import MenuButton from "../ui/menu-button";
|
||||
import Icons from "../ui/icons";
|
||||
import { logout, setTeam, useTeamId } from "@/stores/auth";
|
||||
import { useUser } from "@/hooks/useUser";
|
||||
import TeamForm, { teamFormModal } from "@/pages/team/components/team-form";
|
||||
import Avatar from "../ui/avatar";
|
||||
|
||||
const UserMenuButton = () => {
|
||||
const user = useUser();
|
||||
@ -31,16 +24,21 @@ const UserMenuButton = () => {
|
||||
borderWidth={0}
|
||||
justifyContent="flex-start"
|
||||
borderRadius="$10"
|
||||
py={0}
|
||||
px="$2"
|
||||
p={0}
|
||||
gap="$1"
|
||||
>
|
||||
<Avatar circular size="$3">
|
||||
<Avatar.Fallback bg="$blue4" />
|
||||
</Avatar>
|
||||
<Avatar size="$4" src={user?.image} />
|
||||
|
||||
<View flex={1}>
|
||||
<Text textAlign='left' numberOfLines={1}>{user?.name}</Text>
|
||||
<Text textAlign='left' numberOfLines={1} fontWeight="600" mt="$1.5">
|
||||
<Text textAlign="left" numberOfLines={1}>
|
||||
{user?.name}
|
||||
</Text>
|
||||
<Text
|
||||
textAlign="left"
|
||||
numberOfLines={1}
|
||||
fontWeight="600"
|
||||
mt="$1.5"
|
||||
>
|
||||
{team ? `${team.icon} ${team.name}` : "Personal"}
|
||||
</Text>
|
||||
</View>
|
||||
|
25
frontend/components/ui/avatar.tsx
Normal file
25
frontend/components/ui/avatar.tsx
Normal 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;
|
19
frontend/hooks/useServerConfig.ts
Normal file
19
frontend/hooks/useServerConfig.ts
Normal 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;
|
||||
},
|
||||
});
|
||||
};
|
@ -31,8 +31,10 @@
|
||||
"@xterm/addon-fit": "^0.10.0",
|
||||
"@xterm/xterm": "^5.5.0",
|
||||
"expo": "~52.0.6",
|
||||
"expo-auth-session": "~6.0.0",
|
||||
"expo-blur": "~14.0.1",
|
||||
"expo-constants": "~17.0.3",
|
||||
"expo-crypto": "~14.0.1",
|
||||
"expo-font": "~13.0.0",
|
||||
"expo-haptics": "~14.0.0",
|
||||
"expo-linking": "~7.0.2",
|
||||
|
42
frontend/pages/auth/components/login-github.tsx
Normal file
42
frontend/pages/auth/components/login-github.tsx
Normal 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;
|
@ -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("/");
|
||||
},
|
||||
});
|
||||
};
|
||||
|
@ -6,17 +6,21 @@ import { useZForm } from "@/hooks/useZForm";
|
||||
import { Link, router, Stack } from "expo-router";
|
||||
import Button from "@/components/ui/button";
|
||||
import ThemeSwitcher from "@/components/containers/theme-switcher";
|
||||
import { useMutation } from "@tanstack/react-query";
|
||||
import { z } from "zod";
|
||||
import * as WebBrowser from "expo-web-browser";
|
||||
import { ErrorAlert } from "@/components/ui/alert";
|
||||
import { loginSchema } from "./schema";
|
||||
import Icons from "@/components/ui/icons";
|
||||
import tamaguiConfig from "@/tamagui.config";
|
||||
import { useLoginMutation } from "./hooks";
|
||||
import LoginGithubButton from "./components/login-github";
|
||||
import { useServerConfig } from "@/hooks/useServerConfig";
|
||||
|
||||
WebBrowser.maybeCompleteAuthSession();
|
||||
|
||||
export default function LoginPage() {
|
||||
const form = useZForm(loginSchema);
|
||||
const login = useLoginMutation();
|
||||
const { data: oauthList } = useServerConfig("oauth");
|
||||
|
||||
const onSubmit = form.handleSubmit((values) => {
|
||||
login.mutate(values);
|
||||
@ -98,6 +102,16 @@ export default function LoginPage() {
|
||||
</Link>
|
||||
</XStack>
|
||||
|
||||
{oauthList?.length > 0 && (
|
||||
<>
|
||||
<Text textAlign="center" fontSize="$3">
|
||||
or
|
||||
</Text>
|
||||
|
||||
{oauthList.includes("github") && <LoginGithubButton />}
|
||||
</>
|
||||
)}
|
||||
|
||||
<Separator w="100%" />
|
||||
|
||||
<Button
|
||||
|
@ -1,5 +1,5 @@
|
||||
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 Icons from "@/components/ui/icons";
|
||||
import SearchInput from "@/components/ui/search-input";
|
||||
@ -7,6 +7,7 @@ import { useTeamId } from "@/stores/auth";
|
||||
import { changeRoleModal } from "./change-role-form";
|
||||
import { useRemoveMemberMutation } from "../hooks/query";
|
||||
import { showDialog } from "@/hooks/useDialog";
|
||||
import Avatar from "@/components/ui/avatar";
|
||||
|
||||
type Props = {
|
||||
members?: any[];
|
||||
@ -59,11 +60,7 @@ const MemberList = ({ members, allowWrite }: Props) => {
|
||||
title={member.user?.name}
|
||||
subTitle={member.role}
|
||||
pr="$2"
|
||||
icon={
|
||||
<Avatar size="$3" circular>
|
||||
<Avatar.Fallback bg="$blue5" />
|
||||
</Avatar>
|
||||
}
|
||||
icon={<Avatar src={member.user?.image} size="$4" />}
|
||||
iconAfter={
|
||||
allowWrite ? (
|
||||
<MemberActionButton
|
||||
|
45
frontend/pnpm-lock.yaml
generated
45
frontend/pnpm-lock.yaml
generated
@ -49,12 +49,18 @@ importers:
|
||||
expo:
|
||||
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)
|
||||
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:
|
||||
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)
|
||||
expo-constants:
|
||||
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))
|
||||
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:
|
||||
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)
|
||||
@ -3159,6 +3165,11 @@ packages:
|
||||
resolution: {integrity: sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==}
|
||||
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:
|
||||
resolution: {integrity: sha512-WatvD7JVC89EsllXFYcS/rji3ajVzE2B/USo0TqedsETixwyVCQfrrvCdCPQyuKghrxVNEj8bQ/Qbea/RZLYjg==}
|
||||
peerDependencies:
|
||||
@ -3166,6 +3177,12 @@ packages:
|
||||
react: '*'
|
||||
react-native: '*'
|
||||
|
||||
expo-auth-session@6.0.0:
|
||||
resolution: {integrity: sha512-t40IvmUnWPdSFTr/d3FxDo3qbHdt6hPoRApZ9KH8/UoTjkdoSKnxi6W0/svpISDPMi25gB0lNYwy72YUisl1Yw==}
|
||||
peerDependencies:
|
||||
react: '*'
|
||||
react-native: '*'
|
||||
|
||||
expo-blur@14.0.1:
|
||||
resolution: {integrity: sha512-3Q6jFBLbY8n2vwk28ycUC+eIlVhnlqwkXUKk/Lfaj+SGV3AZMQyrixe7OYwJdUfwqETBrnYYMB6uNrJzOSbG+g==}
|
||||
peerDependencies:
|
||||
@ -3179,6 +3196,11 @@ packages:
|
||||
expo: '*'
|
||||
react-native: '*'
|
||||
|
||||
expo-crypto@14.0.1:
|
||||
resolution: {integrity: sha512-/gGpD9UAz8fgZtU08cwwqeQElkFmMy2Hc8lLa9laSjD3YN0XM07zDJyJ+CC1VhQ63G8WpUnq1IHSmaPbbLp+oQ==}
|
||||
peerDependencies:
|
||||
expo: '*'
|
||||
|
||||
expo-file-system@18.0.3:
|
||||
resolution: {integrity: sha512-HKe0dGW3FWYFi1F3THVnTRueTG7j0onmEpUJKRB4UbjeHD2723cn/EutcG216wvrJeebe8w3+00F8Z4xk+9Jrw==}
|
||||
peerDependencies:
|
||||
@ -10010,6 +10032,10 @@ snapshots:
|
||||
jest-message-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):
|
||||
dependencies:
|
||||
'@expo/image-utils': 0.6.3
|
||||
@ -10022,6 +10048,20 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- 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):
|
||||
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)
|
||||
@ -10037,6 +10077,11 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- 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)):
|
||||
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)
|
||||
|
1
go.mod
1
go.mod
@ -12,6 +12,7 @@ require (
|
||||
github.com/stretchr/testify v1.9.0
|
||||
github.com/wailsapp/wails/v2 v2.9.2
|
||||
golang.org/x/crypto v0.28.0
|
||||
golang.org/x/oauth2 v0.24.0
|
||||
gorm.io/datatypes v1.2.4
|
||||
gorm.io/driver/postgres v1.5.9
|
||||
gorm.io/driver/sqlite v1.5.6
|
||||
|
4
go.sum
4
go.sum
@ -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/sqlexp v0.1.0 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei6A=
|
||||
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/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
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.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ=
|
||||
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/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
|
@ -8,6 +8,7 @@ import (
|
||||
"github.com/gofiber/fiber/v2/middleware/cors"
|
||||
"github.com/joho/godotenv"
|
||||
"rul.sh/vaulterm/server/app/auth"
|
||||
"rul.sh/vaulterm/server/app/server"
|
||||
"rul.sh/vaulterm/server/db"
|
||||
"rul.sh/vaulterm/server/middleware"
|
||||
"rul.sh/vaulterm/server/utils"
|
||||
@ -31,12 +32,7 @@ func NewApp() *fiber.App {
|
||||
app.Use(cors.New())
|
||||
|
||||
// Server info
|
||||
app.Get("/server", func(c *fiber.Ctx) error {
|
||||
return c.JSON(&fiber.Map{
|
||||
"name": "Vaulterm",
|
||||
"version": "0.0.1",
|
||||
})
|
||||
})
|
||||
server.Router(app)
|
||||
|
||||
// Health check
|
||||
app.Get("/health-check", func(c *fiber.Ctx) error {
|
||||
|
124
server/app/auth/oauth_github.go
Normal file
124
server/app/auth/oauth_github.go
Normal 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,
|
||||
})
|
||||
}
|
@ -23,6 +23,12 @@ func (r *Auth) FindUser(username string, email string) (*models.User, 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) {
|
||||
sessionId, err := lib.GenerateSessionID(20)
|
||||
if err != nil {
|
||||
|
@ -15,6 +15,10 @@ func Router(app *fiber.App) {
|
||||
router.Get("/user", middleware.Protected(), getUser)
|
||||
router.Post("/register", register)
|
||||
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 {
|
||||
|
29
server/app/server/router.go
Normal file
29
server/app/server/router.go
Normal 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)
|
||||
}
|
@ -7,6 +7,7 @@ import (
|
||||
var Models = []interface{}{
|
||||
&models.User{},
|
||||
&models.UserSession{},
|
||||
&models.UserAccount{},
|
||||
&models.Keychain{},
|
||||
&models.Host{},
|
||||
&models.Team{},
|
||||
|
@ -5,6 +5,8 @@ import "slices"
|
||||
const (
|
||||
UserRoleUser = "user"
|
||||
UserRoleAdmin = "admin"
|
||||
|
||||
UserAccountTypeGithub = "github"
|
||||
)
|
||||
|
||||
type User struct {
|
||||
@ -15,8 +17,10 @@ type User struct {
|
||||
Password string `json:"-"`
|
||||
Email string `json:"email" gorm:"unique"`
|
||||
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"`
|
||||
Accounts []*UserAccount `json:"accounts" gorm:"foreignKey:UserID"`
|
||||
|
||||
Timestamps
|
||||
SoftDeletes
|
||||
@ -31,6 +35,20 @@ type UserSession struct {
|
||||
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 {
|
||||
return u.Role == UserRoleAdmin
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user