mirror of
https://github.com/khairul169/vaulterm.git
synced 2025-04-28 16:49:39 +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`
|
# 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=
|
||||||
|
@ -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 (
|
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>
|
||||||
|
|
||||||
|
@ -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>
|
||||||
|
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/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",
|
||||||
|
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 { 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
|
||||||
|
@ -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
|
||||||
|
45
frontend/pnpm-lock.yaml
generated
45
frontend/pnpm-lock.yaml
generated
@ -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
1
go.mod
@ -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
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/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=
|
||||||
|
@ -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 {
|
||||||
|
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
|
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 {
|
||||||
|
@ -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 {
|
||||||
|
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{}{
|
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{},
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user