Skip to content

Commit 59af1fb

Browse files
committed
add: google auth
1 parent 5ebce9e commit 59af1fb

File tree

10 files changed

+155
-145
lines changed

10 files changed

+155
-145
lines changed

app.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,12 @@
3636
{
3737
"initialOrientation": "DEFAULT"
3838
}
39+
],
40+
[
41+
"@react-native-google-signin/google-signin",
42+
{
43+
"iosUrlScheme": "com.googleusercontent.apps._some_id_here_"
44+
}
3945
]
4046
],
4147
"experiments": {

app/(home)/_layout.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import { Tabs } from "expo-router";
22
import React from "react";
33

4-
import { TabBarIcon } from "@/components/navigation/TabBarIcon";
54
import { Colors } from "@/constants/Colors";
65
import { useColors } from "@/hooks/useColors";
76
import { AntDesign, Feather } from "@expo/vector-icons";

app/(home)/all-anime.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,9 @@ import { API_URL } from "@/constants/Strings";
77
import { useColors } from "@/hooks/useColors";
88
import { axiosIn } from "@/utils/axios";
99

10-
import { useInfiniteQuery, useQuery } from "@tanstack/react-query";
10+
import { useInfiniteQuery } from "@tanstack/react-query";
1111
import { useMemo } from "react";
12-
import { ActivityIndicator, Pressable, StyleSheet, View } from "react-native";
12+
import { ActivityIndicator, StyleSheet, View } from "react-native";
1313
import { FlatList } from "react-native-gesture-handler";
1414
import { SafeAreaView } from "react-native-safe-area-context";
1515

app/(home)/user.tsx

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
1-
// import GoogleLogin from "@/components/auth/GoogleLogin";
1+
import GoogleLogin from "@/components/auth/GoogleLogin";
22
import SafeAreaWrapper from "@/components/SafeAreaWrapper";
33
import { CustomText } from "@/components/ui";
44
import { tokenAtom } from "@/store/auth";
5+
import { GoogleSignin } from "@react-native-google-signin/google-signin";
56
import { useAtom, useAtomValue } from "jotai";
7+
import { Alert } from "react-native";
68
import { TouchableOpacity } from "react-native-gesture-handler";
79

810
export default function AllAnime() {
@@ -12,8 +14,15 @@ export default function AllAnime() {
1214
<SafeAreaWrapper>
1315
<CustomText>All Anime</CustomText>
1416

15-
{/* {!token ? <GoogleLogin /> : <CustomText>Logged in</CustomText>} */}
16-
<TouchableOpacity>
17+
{!token ? <GoogleLogin /> : <CustomText>Logged in</CustomText>}
18+
<TouchableOpacity
19+
onPress={async () => {
20+
await GoogleSignin.signOut();
21+
22+
setToken(null);
23+
Alert.alert("Sign out success!");
24+
}}
25+
>
1726
<CustomText>Sign Out</CustomText>
1827
</TouchableOpacity>
1928
</SafeAreaWrapper>

app/_layout.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,18 +15,18 @@ import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
1515

1616
import { GestureHandlerRootView } from "react-native-gesture-handler";
1717
import { BottomSheetModalProvider } from "@gorhom/bottom-sheet";
18-
// import { GoogleSignin } from "@react-native-google-signin/google-signin";
18+
import { GoogleSignin } from "@react-native-google-signin/google-signin";
1919

2020
SplashScreen.preventAutoHideAsync();
2121
ScreenOrientation.unlockAsync();
2222

2323
const queryClient = new QueryClient();
2424

2525
export default function RootLayout() {
26-
// GoogleSignin.configure({
27-
// scopes: ["https://www.googleapis.com/auth/drive.readonly"],
28-
// webClientId: process.env.EXPO_PUBLIC_GOOGLE_WEB_CLIENT_ID,
29-
// });
26+
GoogleSignin.configure({
27+
// scopes: ["https://www.googleapis.com/auth/drive.readonly"],
28+
webClientId: process.env.EXPO_PUBLIC_GOOGLE_WEB_CLIENT_ID,
29+
});
3030

3131
const [loaded, error] = useFonts({
3232
Poppins_400Regular,

components/GlobalLoading.tsx

Lines changed: 0 additions & 34 deletions
This file was deleted.

components/auth/GoogleLogin.tsx

Lines changed: 74 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -1,78 +1,79 @@
1-
// import { View } from "react-native";
2-
// import { API_URL } from "@/constants/Strings";
3-
// import { axiosIn } from "@/utils/axios";
4-
// import { supabase } from "@/utils/supabase";
5-
// import {
6-
// GoogleSignin,
7-
// GoogleSigninButton,
8-
// statusCodes,
9-
// } from "@react-native-google-signin/google-signin";
10-
// import { Alert } from "react-native";
11-
// import { useSetAtom } from "jotai";
12-
// import { tokenAtom } from "@/store/auth";
1+
import { View } from "react-native";
2+
import { API_URL } from "@/constants/Strings";
3+
import { axiosIn } from "@/utils/axios";
4+
import { supabase } from "@/utils/supabase";
5+
import {
6+
GoogleSignin,
7+
GoogleSigninButton,
8+
statusCodes,
9+
} from "@react-native-google-signin/google-signin";
10+
import { Alert } from "react-native";
11+
import { useSetAtom } from "jotai";
12+
import { tokenAtom } from "@/store/auth";
1313

14-
// export default function GoogleLogin() {
15-
// const setToken = useSetAtom(tokenAtom);
14+
export default function GoogleLogin() {
15+
const setToken = useSetAtom(tokenAtom);
1616

17-
// return (
18-
// <View>
19-
// <GoogleSigninButton
20-
// size={GoogleSigninButton.Size.Wide}
21-
// color={GoogleSigninButton.Color.Dark}
22-
// onPress={async () => {
23-
// try {
24-
// await GoogleSignin.hasPlayServices();
25-
// const userInfo = await GoogleSignin.signIn();
26-
// // console.log(userInfo);
17+
return (
18+
<View>
19+
<GoogleSigninButton
20+
size={GoogleSigninButton.Size.Wide}
21+
color={GoogleSigninButton.Color.Dark}
22+
onPress={async () => {
23+
try {
24+
await GoogleSignin.hasPlayServices();
25+
const userInfo = await GoogleSignin.signIn();
26+
// console.log(userInfo);
2727

28-
// if (userInfo.idToken) {
29-
// const { data, error } = await supabase.auth.signInWithIdToken({
30-
// provider: "google",
31-
// token: userInfo.idToken,
32-
// });
28+
if (userInfo.idToken) {
29+
const { data, error } = await supabase.auth.signInWithIdToken({
30+
provider: "google",
31+
token: userInfo.idToken,
32+
});
3333

34-
// if (error) {
35-
// console.log("ERROR SUPABASE LOGIN: ", error);
36-
// Alert.alert("Error logging in with Supabase!");
37-
// } else {
38-
// const getToken = await axiosIn.post(
39-
// `${API_URL}/anime/auth/login`,
40-
// {
41-
// email: data.user.email,
42-
// },
43-
// {
44-
// headers: {
45-
// Authorization: `Bearer ${data.session.access_token}`,
46-
// },
47-
// }
48-
// );
34+
if (error) {
35+
console.log("ERROR SUPABASE LOGIN: ", error);
36+
Alert.alert("Error logging in with Supabase!");
37+
} else {
38+
const getToken = await axiosIn.post(
39+
`${API_URL}/anime/auth/login`,
40+
{
41+
email: data.user.email,
42+
},
43+
{
44+
headers: {
45+
Authorization: `Bearer ${data.session.access_token}`,
46+
},
47+
}
48+
);
4949

50-
// if (getToken.data && getToken.data.status == true) {
51-
// console.log("TOKEN: ", getToken.data);
52-
// setToken(getToken.data.data.token);
53-
// Alert.alert("Token found!");
54-
// } else {
55-
// console.log("NO TOKEN: ", getToken.data);
56-
// Alert.alert("No token found!");
57-
// }
58-
// }
59-
// } else {
60-
// throw new Error("no ID token present!");
61-
// }
62-
// } catch (error: any) {
63-
// console.log(error);
64-
// if (error.code === statusCodes.SIGN_IN_CANCELLED) {
65-
// // user cancelled the login flow
66-
// } else if (error.code === statusCodes.IN_PROGRESS) {
67-
// // operation (e.g. sign in) is in progress already
68-
// } else if (error.code === statusCodes.PLAY_SERVICES_NOT_AVAILABLE) {
69-
// // play services not available or outdated
70-
// } else {
71-
// // some other error happened
72-
// }
73-
// }
74-
// }}
75-
// />
76-
// </View>
77-
// );
78-
// }
50+
if (getToken.data && getToken.data.status == true) {
51+
console.log("TOKEN: ", getToken.data);
52+
setToken(getToken.data.data.token);
53+
Alert.alert("Token found!");
54+
} else {
55+
console.log("NO TOKEN: ", getToken.data);
56+
Alert.alert("No token found!");
57+
}
58+
}
59+
} else {
60+
throw new Error("no ID token present!");
61+
}
62+
} catch (error: any) {
63+
Alert.alert(error.toString());
64+
console.log(error);
65+
if (error.code === statusCodes.SIGN_IN_CANCELLED) {
66+
// user cancelled the login flow
67+
} else if (error.code === statusCodes.IN_PROGRESS) {
68+
// operation (e.g. sign in) is in progress already
69+
} else if (error.code === statusCodes.PLAY_SERVICES_NOT_AVAILABLE) {
70+
// play services not available or outdated
71+
} else {
72+
// some other error happened
73+
}
74+
}
75+
}}
76+
/>
77+
</View>
78+
);
79+
}

store/auth.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
import { atomWithStorage, createJSONStorage } from "jotai/utils";
22
import * as SecureStore from "expo-secure-store";
33

4-
const secureStore = createJSONStorage<string>(() => ({
4+
const secureStore = createJSONStorage<string | null>(() => ({
55
getItem: async (key: string) => {
66
const value = await SecureStore.getItemAsync(key);
7-
return value;
7+
return value !== null ? value : "";
88
},
99
setItem: async (key: string, value: string) => {
1010
await SecureStore.setItemAsync(key, value);
@@ -14,4 +14,8 @@ const secureStore = createJSONStorage<string>(() => ({
1414
},
1515
}));
1616

17-
export const tokenAtom = atomWithStorage("token", "", secureStore);
17+
export const tokenAtom = atomWithStorage<string | null>(
18+
"token",
19+
null,
20+
secureStore
21+
);

utils/axios.ts

Lines changed: 48 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
import axios, { AxiosResponse } from "axios";
22
import crypto from "react-native-quick-crypto";
3-
import { Buffer } from "buffer";
4-
global.Buffer = Buffer;
53

64
export const axiosIn = axios.create({
75
// headers: {
@@ -34,26 +32,61 @@ axiosIn.interceptors.response.use(
3432
}
3533
);
3634

37-
// Menggunakan ENCRYPTION_KEY yang sama seperti di server
38-
const ENCRYPTION_KEY = Buffer.from(
39-
process.env.EXPO_PUBLIC_ENCRYPTION_KEY as string,
40-
"hex"
35+
const ENCRYPTION_KEY = hexStringToUint8Array(
36+
process.env.EXPO_PUBLIC_ENCRYPTION_KEY || ""
4137
);
4238
const IV_LENGTH = 12; // Panjang IV yang digunakan untuk AES-256-GCM
4339

40+
if (ENCRYPTION_KEY.length !== 32) {
41+
throw new Error(
42+
"Invalid ENCRYPTION_KEY length. It should be 32 bytes for AES-256-GCM."
43+
);
44+
}
45+
46+
// Fungsi untuk mengonversi string hexadecimal ke Uint8Array
47+
function hexStringToUint8Array(hexString: string): Uint8Array {
48+
if (hexString.length % 2 !== 0) {
49+
throw new Error("Hex string length must be even.");
50+
}
51+
52+
const arrayBuffer = new Uint8Array(hexString.length / 2);
53+
for (let i = 0; i < hexString.length; i += 2) {
54+
arrayBuffer[i / 2] = parseInt(hexString.substring(i, i + 2), 16);
55+
}
56+
57+
return arrayBuffer;
58+
}
59+
4460
// Fungsi untuk mendekripsi data
4561
const decrypt = (encryptedText: string): string => {
46-
const [ivHex, authTagHex, encryptedBase64] = encryptedText.split(":");
62+
try {
63+
const [ivHex, authTagHex, _encryptedBase64] = encryptedText.split(":");
64+
65+
if (!ivHex || !authTagHex || !_encryptedBase64) {
66+
throw new Error(
67+
"Invalid input format. Expected format is 'iv:authTag:encryptedText'."
68+
);
69+
}
4770

48-
const iv = Buffer.from(ivHex, "hex");
49-
const authTag = Buffer.from(authTagHex, "hex");
50-
const encryptedTextBuffer = Buffer.from(encryptedBase64, "base64");
71+
const encryptedBase64 =
72+
_encryptedBase64.replace(/-/g, "+").replace(/_/g, "/") +
73+
"=".repeat((4 - (_encryptedBase64.length % 4)) % 4);
5174

52-
const decipher = crypto.createDecipheriv("aes-256-gcm", ENCRYPTION_KEY, iv);
53-
decipher.setAuthTag(authTag);
75+
const iv = hexStringToUint8Array(ivHex);
76+
const authTag = hexStringToUint8Array(authTagHex);
77+
const encryptedTextBuffer = Uint8Array.from(atob(encryptedBase64), (c) =>
78+
c.charCodeAt(0)
79+
);
5480

55-
let decrypted = decipher.update(encryptedTextBuffer, undefined, "utf8");
56-
decrypted += decipher.final("utf8");
81+
const decipher = crypto.createDecipheriv("aes-256-gcm", ENCRYPTION_KEY, iv);
82+
decipher.setAuthTag(authTag);
5783

58-
return decrypted;
84+
let decrypted = decipher.update(encryptedTextBuffer, undefined, "utf8");
85+
decrypted += decipher.final("utf8");
86+
87+
return decrypted;
88+
} catch (error) {
89+
console.error("Decryption failed:", error);
90+
throw new Error("Failed to decrypt data.");
91+
}
5992
};

utils/supabase.ts

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,9 @@
11
import "react-native-url-polyfill/auto";
2-
import AsyncStorage from "@react-native-async-storage/async-storage";
32
import { createClient } from "@supabase/supabase-js";
43

54
const supabaseUrl = process.env.EXPO_PUBLIC_SUPABASE_URL as string;
65
const supabaseAnonKey = process.env.EXPO_PUBLIC_SUPABASE_ANON_KEY as string;
76

87
// console.log(supabaseUrl, supabaseAnonKey);
98

10-
export const supabase = createClient(supabaseUrl, supabaseAnonKey, {
11-
auth: {
12-
storage: AsyncStorage,
13-
autoRefreshToken: true,
14-
persistSession: true,
15-
detectSessionInUrl: false,
16-
},
17-
});
9+
export const supabase = createClient(supabaseUrl, supabaseAnonKey);

0 commit comments

Comments
 (0)