From 95f9f76edd6035e239c6ece383637e0a4bec6d9d Mon Sep 17 00:00:00 2001 From: Khairul Hidayat Date: Sun, 17 Nov 2024 21:15:25 +0700 Subject: [PATCH] fix: native gitlab & github login --- frontend/.gitignore | 2 + frontend/app.json | 79 ++++++++++--------- frontend/lib/api.ts | 5 +- frontend/package.json | 5 +- .../pages/auth/components/login-github.tsx | 6 +- .../pages/auth/components/login-gitlab.tsx | 6 +- frontend/pages/auth/hooks.ts | 7 +- server/app/auth/oauth_github.go | 28 +++---- server/app/auth/oauth_gitlab.go | 59 +++++--------- server/app/auth/router.go | 7 +- 10 files changed, 92 insertions(+), 112 deletions(-) diff --git a/frontend/.gitignore b/frontend/.gitignore index 451f25f..c91377b 100644 --- a/frontend/.gitignore +++ b/frontend/.gitignore @@ -22,3 +22,5 @@ expo-env.d.ts !.env.example *.apk *.aab +android/ +ios/ diff --git a/frontend/app.json b/frontend/app.json index d8a497f..97efd7f 100644 --- a/frontend/app.json +++ b/frontend/app.json @@ -1,41 +1,42 @@ { - "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" - } + "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, + "bundleIdentifier": "sh.rul.vaulterm" + }, + "android": { + "package": "sh.rul.vaulterm", + "adaptiveIcon": { + "foregroundImage": "./assets/images/adaptive-icon.png", + "backgroundColor": "#ffffff" } -} \ No newline at end of file + }, + "web": { + "bundler": "metro", + "output": "single", + "favicon": "./assets/images/favicon.png" + }, + "plugins": [ + "expo-router" + ], + "experiments": { + "typedRoutes": true + }, + "extra": { + "eas": { + "projectId": "3e0112c1-f0ed-423c-b5cf-95633f23f6dc" + } + } +} diff --git a/frontend/lib/api.ts b/frontend/lib/api.ts index 4cde527..a8dc365 100644 --- a/frontend/lib/api.ts +++ b/frontend/lib/api.ts @@ -27,8 +27,9 @@ const api = ofetch.create({ throw new Error("Unauthorized"); } - if (error.response._data) { - const message = error.response._data.message; + const data = error.response._data; + if (data) { + const message = typeof data === "string" ? data : data?.message; throw new Error(message || "Something went wrong"); } }, diff --git a/frontend/package.json b/frontend/package.json index d51d3c9..5f04e83 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -6,8 +6,9 @@ "scripts": { "start": "expo start", "reset-project": "node ./scripts/reset-project.js", - "android": "expo start --android", - "ios": "expo start --ios", + "prebuild": "expo prebuild", + "android": "expo run:android", + "ios": "expo run:ios", "web": "expo start --web", "test": "jest --watchAll", "lint": "expo lint", diff --git a/frontend/pages/auth/components/login-github.tsx b/frontend/pages/auth/components/login-github.tsx index 14a92ca..300a144 100644 --- a/frontend/pages/auth/components/login-github.tsx +++ b/frontend/pages/auth/components/login-github.tsx @@ -1,9 +1,9 @@ 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"; +import { scheme } from "@/app.json"; const LoginGithubButton = () => { const { data: clientId } = useServerConfig("github_client_id"); @@ -20,7 +20,7 @@ const LoginGithubButton = () => { { clientId: clientId, scopes: ["identity"], - redirectUri: makeRedirectUri({ scheme: appConfig.scheme }), + redirectUri: makeRedirectUri({ scheme, path: "auth/login" }), }, discovery ); @@ -28,7 +28,7 @@ const LoginGithubButton = () => { useEffect(() => { if (response?.type === "success") { const { code } = response.params; - oauth.mutate(code); + oauth.mutate({ code }); } }, [response]); diff --git a/frontend/pages/auth/components/login-gitlab.tsx b/frontend/pages/auth/components/login-gitlab.tsx index aad4b2f..6113f0d 100644 --- a/frontend/pages/auth/components/login-gitlab.tsx +++ b/frontend/pages/auth/components/login-gitlab.tsx @@ -1,8 +1,9 @@ import React, { useEffect, useMemo } from "react"; -import { useAuthRequest } from "expo-auth-session"; +import { makeRedirectUri, useAuthRequest } from "expo-auth-session"; import { Button } from "tamagui"; import { useOAuthCallback } from "../hooks"; import { useServerConfig } from "@/hooks/useServerConfig"; +import { scheme } from "@/app.json"; const LoginGitlabButton = () => { const { data: clientId } = useServerConfig("gitlab_client_id"); @@ -19,8 +20,7 @@ const LoginGitlabButton = () => { { clientId, scopes: ["read_user"], - // redirectUri: makeRedirectUri({ scheme: appConfig.scheme }), - redirectUri: "http://localhost:8081", + redirectUri: makeRedirectUri({ scheme, path: "auth/login" }), }, discovery ); diff --git a/frontend/pages/auth/hooks.ts b/frontend/pages/auth/hooks.ts index 4e5784d..d58b1dc 100644 --- a/frontend/pages/auth/hooks.ts +++ b/frontend/pages/auth/hooks.ts @@ -45,14 +45,17 @@ export const useRegisterMutation = () => { export const useOAuthCallback = (type: string) => { return useMutation({ - mutationFn: async (params: { code: string; verifier?: string }) => { - const res = await api(`/auth/oauth/${type}/callback`, { params }); + mutationFn: async (body: { code: string; verifier?: string }) => { + const res = await api(`/auth/${type}`, { body, method: "POST" }); const { data } = loginResultSchema.safeParse(res); if (!data) { throw new Error("Invalid response!"); } return data; }, + onError: (err) => { + console.log(err); + }, onSuccess(data) { authStore.setState({ token: data.sessionId }); router.replace("/"); diff --git a/server/app/auth/oauth_github.go b/server/app/auth/oauth_github.go index d476529..a6ad767 100644 --- a/server/app/auth/oauth_github.go +++ b/server/app/auth/oauth_github.go @@ -27,28 +27,26 @@ func getGithubConfig() *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", - RedirectURL: "http://localhost:8081", - Scopes: []string{"read:user"}, + RedirectURL: "vaulterm://auth/login", + Scopes: []string{"read:user"}, } return githubCfg } -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 == "" { + var body struct { + Code string `json:"code"` + } + if err := c.BodyParser(&body); err != nil { + return c.Status(fiber.StatusBadRequest).SendString("Failed to parse request body") + } + if body.Code == "" { return c.Status(fiber.StatusBadRequest).SendString("Missing code") } // Exchange code for a token cfg := getGithubConfig() - token, err := cfg.Exchange(c.Context(), code) + token, err := cfg.Exchange(c.Context(), body.Code) if err != nil { return c.Status(fiber.StatusInternalServerError).SendString("Failed to exchange token") } @@ -61,10 +59,10 @@ func githubCallback(c *fiber.Ctx) error { } defer resp.Body.Close() - body, _ := io.ReadAll(resp.Body) + data, _ := io.ReadAll(resp.Body) if resp.StatusCode != 200 { return c.Status(fiber.StatusInternalServerError). - SendString(fmt.Sprintf("GitHub API error: %s", string(body))) + SendString(fmt.Sprintf("GitHub API error: %s", string(data))) } // Parse user info @@ -76,7 +74,7 @@ func githubCallback(c *fiber.Ctx) error { Email string `json:"email"` } - if err := json.Unmarshal(body, &user); err != nil { + if err := json.Unmarshal(data, &user); err != nil { return c.Status(fiber.StatusInternalServerError).SendString("Failed to parse user info") } diff --git a/server/app/auth/oauth_gitlab.go b/server/app/auth/oauth_gitlab.go index 442ae27..6fd352b 100644 --- a/server/app/auth/oauth_gitlab.go +++ b/server/app/auth/oauth_gitlab.go @@ -5,7 +5,6 @@ import ( "errors" "fmt" "io" - "log" "os" "strconv" @@ -17,61 +16,39 @@ import ( "rul.sh/vaulterm/server/utils" ) -type GitlabCfg struct { - oauth2.Config - verifier string - challenge string -} +var gitlabCfg *oauth2.Config -var gitlabCfg *GitlabCfg - -func getGitlabConfig() *GitlabCfg { +func getGitlabConfig() *oauth2.Config { if gitlabCfg != nil { return gitlabCfg } - oauthCfg := oauth2.Config{ + gitlabCfg = &oauth2.Config{ ClientID: os.Getenv("GITLAB_CLIENT_ID"), ClientSecret: os.Getenv("GITLAB_CLIENT_SECRET"), Endpoint: gitlab.Endpoint, - // RedirectURL: "http://localhost:3000/auth/oauth/gitlab/callback", - RedirectURL: "http://localhost:8081", - Scopes: []string{"read_user"}, - } - verifier := oauth2.GenerateVerifier() - challenge := oauth2.S256ChallengeFromVerifier(verifier) - - gitlabCfg = &GitlabCfg{ - Config: oauthCfg, - verifier: verifier, - challenge: challenge, + RedirectURL: "vaulterm://auth/login", + Scopes: []string{"read_user"}, } return gitlabCfg } -func gitlabRedir(c *fiber.Ctx) error { - // Redirect to Gitlab login page - url := getGitlabConfig(). - AuthCodeURL("login", oauth2.S256ChallengeOption(getGitlabConfig().verifier)) - return c.Redirect(url) -} - func gitlabCallback(c *fiber.Ctx) error { - cfg := getGitlabConfig() - code := c.Query("code") - verifier := c.Query("verifier") - - if code == "" { - return c.Status(fiber.StatusBadRequest).SendString("Missing code") + var body struct { + Code string `json:"code"` + Verifier string `json:"verifier"` } - if verifier == "" { - verifier = cfg.verifier + if err := c.BodyParser(&body); err != nil { + return c.Status(fiber.StatusBadRequest).SendString("Failed to parse request body") + } + if body.Code == "" || body.Verifier == "" { + return c.Status(fiber.StatusBadRequest).SendString("Missing code or verifier") } // Exchange code for a token - token, err := cfg.Exchange(c.Context(), code, oauth2.VerifierOption(verifier)) + cfg := getGitlabConfig() + token, err := cfg.Exchange(c.Context(), body.Code, oauth2.VerifierOption(body.Verifier)) if err != nil { - log.Println(token, err) return c.Status(fiber.StatusInternalServerError).SendString("Failed to exchange token") } @@ -83,10 +60,10 @@ func gitlabCallback(c *fiber.Ctx) error { } defer resp.Body.Close() - body, _ := io.ReadAll(resp.Body) + data, _ := io.ReadAll(resp.Body) if resp.StatusCode != 200 { return c.Status(fiber.StatusInternalServerError). - SendString(fmt.Sprintf("Gitlab API error: %s", string(body))) + SendString(fmt.Sprintf("Gitlab API error: %s", string(data))) } // Parse user info @@ -98,7 +75,7 @@ func gitlabCallback(c *fiber.Ctx) error { Email string `json:"email"` } - if err := json.Unmarshal(body, &user); err != nil { + if err := json.Unmarshal(data, &user); err != nil { return c.Status(fiber.StatusInternalServerError).SendString("Failed to parse user info") } diff --git a/server/app/auth/router.go b/server/app/auth/router.go index a4dc1b2..032d362 100644 --- a/server/app/auth/router.go +++ b/server/app/auth/router.go @@ -16,11 +16,8 @@ func Router(app *fiber.App) { router.Post("/register", register) router.Post("/logout", middleware.Protected(), logout) - oauth := router.Group("/oauth") - oauth.Get("/github", githubRedir) - oauth.Get("/github/callback", githubCallback) - oauth.Get("/gitlab", gitlabRedir) - oauth.Get("/gitlab/callback", gitlabCallback) + router.Post("/github", githubCallback) + router.Post("/gitlab", gitlabCallback) } func login(c *fiber.Ctx) error {