mirror of
https://github.com/khairul169/vaulterm.git
synced 2025-04-28 16:49:39 +07:00
fix: native gitlab & github login
This commit is contained in:
parent
83d0ed380d
commit
95f9f76edd
2
frontend/.gitignore
vendored
2
frontend/.gitignore
vendored
@ -22,3 +22,5 @@ expo-env.d.ts
|
|||||||
!.env.example
|
!.env.example
|
||||||
*.apk
|
*.apk
|
||||||
*.aab
|
*.aab
|
||||||
|
android/
|
||||||
|
ios/
|
||||||
|
@ -1,41 +1,42 @@
|
|||||||
{
|
{
|
||||||
"name": "Vaulterm",
|
"name": "Vaulterm",
|
||||||
"slug": "vaulterm",
|
"slug": "vaulterm",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"orientation": "portrait",
|
"orientation": "portrait",
|
||||||
"icon": "./assets/images/icon.png",
|
"icon": "./assets/images/icon.png",
|
||||||
"scheme": "vaulterm",
|
"scheme": "vaulterm",
|
||||||
"userInterfaceStyle": "automatic",
|
"userInterfaceStyle": "automatic",
|
||||||
"newArchEnabled": true,
|
"newArchEnabled": true,
|
||||||
"splash": {
|
"splash": {
|
||||||
"image": "./assets/images/splash.png",
|
"image": "./assets/images/splash.png",
|
||||||
"resizeMode": "contain",
|
"resizeMode": "contain",
|
||||||
"backgroundColor": "#ffffff"
|
"backgroundColor": "#ffffff"
|
||||||
},
|
},
|
||||||
"ios": {
|
"ios": {
|
||||||
"supportsTablet": true
|
"supportsTablet": true,
|
||||||
},
|
"bundleIdentifier": "sh.rul.vaulterm"
|
||||||
"android": {
|
},
|
||||||
"package": "sh.rul.vaulterm",
|
"android": {
|
||||||
"adaptiveIcon": {
|
"package": "sh.rul.vaulterm",
|
||||||
"foregroundImage": "./assets/images/adaptive-icon.png",
|
"adaptiveIcon": {
|
||||||
"backgroundColor": "#ffffff"
|
"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"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
"web": {
|
||||||
|
"bundler": "metro",
|
||||||
|
"output": "single",
|
||||||
|
"favicon": "./assets/images/favicon.png"
|
||||||
|
},
|
||||||
|
"plugins": [
|
||||||
|
"expo-router"
|
||||||
|
],
|
||||||
|
"experiments": {
|
||||||
|
"typedRoutes": true
|
||||||
|
},
|
||||||
|
"extra": {
|
||||||
|
"eas": {
|
||||||
|
"projectId": "3e0112c1-f0ed-423c-b5cf-95633f23f6dc"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -27,8 +27,9 @@ const api = ofetch.create({
|
|||||||
throw new Error("Unauthorized");
|
throw new Error("Unauthorized");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (error.response._data) {
|
const data = error.response._data;
|
||||||
const message = error.response._data.message;
|
if (data) {
|
||||||
|
const message = typeof data === "string" ? data : data?.message;
|
||||||
throw new Error(message || "Something went wrong");
|
throw new Error(message || "Something went wrong");
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -6,8 +6,9 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "expo start",
|
"start": "expo start",
|
||||||
"reset-project": "node ./scripts/reset-project.js",
|
"reset-project": "node ./scripts/reset-project.js",
|
||||||
"android": "expo start --android",
|
"prebuild": "expo prebuild",
|
||||||
"ios": "expo start --ios",
|
"android": "expo run:android",
|
||||||
|
"ios": "expo run:ios",
|
||||||
"web": "expo start --web",
|
"web": "expo start --web",
|
||||||
"test": "jest --watchAll",
|
"test": "jest --watchAll",
|
||||||
"lint": "expo lint",
|
"lint": "expo lint",
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
import React, { useEffect, useMemo } from "react";
|
import React, { useEffect, useMemo } from "react";
|
||||||
import { makeRedirectUri, useAuthRequest } from "expo-auth-session";
|
import { makeRedirectUri, useAuthRequest } from "expo-auth-session";
|
||||||
import appConfig from "@/app.json";
|
|
||||||
import { Button } from "tamagui";
|
import { Button } from "tamagui";
|
||||||
import { useOAuthCallback } from "../hooks";
|
import { useOAuthCallback } from "../hooks";
|
||||||
import { useServerConfig } from "@/hooks/useServerConfig";
|
import { useServerConfig } from "@/hooks/useServerConfig";
|
||||||
|
import { scheme } from "@/app.json";
|
||||||
|
|
||||||
const LoginGithubButton = () => {
|
const LoginGithubButton = () => {
|
||||||
const { data: clientId } = useServerConfig("github_client_id");
|
const { data: clientId } = useServerConfig("github_client_id");
|
||||||
@ -20,7 +20,7 @@ const LoginGithubButton = () => {
|
|||||||
{
|
{
|
||||||
clientId: clientId,
|
clientId: clientId,
|
||||||
scopes: ["identity"],
|
scopes: ["identity"],
|
||||||
redirectUri: makeRedirectUri({ scheme: appConfig.scheme }),
|
redirectUri: makeRedirectUri({ scheme, path: "auth/login" }),
|
||||||
},
|
},
|
||||||
discovery
|
discovery
|
||||||
);
|
);
|
||||||
@ -28,7 +28,7 @@ const LoginGithubButton = () => {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (response?.type === "success") {
|
if (response?.type === "success") {
|
||||||
const { code } = response.params;
|
const { code } = response.params;
|
||||||
oauth.mutate(code);
|
oauth.mutate({ code });
|
||||||
}
|
}
|
||||||
}, [response]);
|
}, [response]);
|
||||||
|
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
import React, { useEffect, useMemo } from "react";
|
import React, { useEffect, useMemo } from "react";
|
||||||
import { useAuthRequest } from "expo-auth-session";
|
import { makeRedirectUri, useAuthRequest } from "expo-auth-session";
|
||||||
import { Button } from "tamagui";
|
import { Button } from "tamagui";
|
||||||
import { useOAuthCallback } from "../hooks";
|
import { useOAuthCallback } from "../hooks";
|
||||||
import { useServerConfig } from "@/hooks/useServerConfig";
|
import { useServerConfig } from "@/hooks/useServerConfig";
|
||||||
|
import { scheme } from "@/app.json";
|
||||||
|
|
||||||
const LoginGitlabButton = () => {
|
const LoginGitlabButton = () => {
|
||||||
const { data: clientId } = useServerConfig("gitlab_client_id");
|
const { data: clientId } = useServerConfig("gitlab_client_id");
|
||||||
@ -19,8 +20,7 @@ const LoginGitlabButton = () => {
|
|||||||
{
|
{
|
||||||
clientId,
|
clientId,
|
||||||
scopes: ["read_user"],
|
scopes: ["read_user"],
|
||||||
// redirectUri: makeRedirectUri({ scheme: appConfig.scheme }),
|
redirectUri: makeRedirectUri({ scheme, path: "auth/login" }),
|
||||||
redirectUri: "http://localhost:8081",
|
|
||||||
},
|
},
|
||||||
discovery
|
discovery
|
||||||
);
|
);
|
||||||
|
@ -45,14 +45,17 @@ export const useRegisterMutation = () => {
|
|||||||
|
|
||||||
export const useOAuthCallback = (type: string) => {
|
export const useOAuthCallback = (type: string) => {
|
||||||
return useMutation({
|
return useMutation({
|
||||||
mutationFn: async (params: { code: string; verifier?: string }) => {
|
mutationFn: async (body: { code: string; verifier?: string }) => {
|
||||||
const res = await api(`/auth/oauth/${type}/callback`, { params });
|
const res = await api(`/auth/${type}`, { body, method: "POST" });
|
||||||
const { data } = loginResultSchema.safeParse(res);
|
const { data } = loginResultSchema.safeParse(res);
|
||||||
if (!data) {
|
if (!data) {
|
||||||
throw new Error("Invalid response!");
|
throw new Error("Invalid response!");
|
||||||
}
|
}
|
||||||
return data;
|
return data;
|
||||||
},
|
},
|
||||||
|
onError: (err) => {
|
||||||
|
console.log(err);
|
||||||
|
},
|
||||||
onSuccess(data) {
|
onSuccess(data) {
|
||||||
authStore.setState({ token: data.sessionId });
|
authStore.setState({ token: data.sessionId });
|
||||||
router.replace("/");
|
router.replace("/");
|
||||||
|
@ -27,28 +27,26 @@ func getGithubConfig() *oauth2.Config {
|
|||||||
ClientID: os.Getenv("GITHUB_CLIENT_ID"),
|
ClientID: os.Getenv("GITHUB_CLIENT_ID"),
|
||||||
ClientSecret: os.Getenv("GITHUB_CLIENT_SECRET"),
|
ClientSecret: os.Getenv("GITHUB_CLIENT_SECRET"),
|
||||||
Endpoint: github.Endpoint,
|
Endpoint: github.Endpoint,
|
||||||
// RedirectURL: "http://localhost:3000/auth/oauth/github/callback",
|
RedirectURL: "vaulterm://auth/login",
|
||||||
RedirectURL: "http://localhost:8081",
|
Scopes: []string{"read:user"},
|
||||||
Scopes: []string{"read:user"},
|
|
||||||
}
|
}
|
||||||
return githubCfg
|
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 {
|
func githubCallback(c *fiber.Ctx) error {
|
||||||
code := c.Query("code")
|
var body struct {
|
||||||
if code == "" {
|
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")
|
return c.Status(fiber.StatusBadRequest).SendString("Missing code")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Exchange code for a token
|
// Exchange code for a token
|
||||||
cfg := getGithubConfig()
|
cfg := getGithubConfig()
|
||||||
token, err := cfg.Exchange(c.Context(), code)
|
token, err := cfg.Exchange(c.Context(), body.Code)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return c.Status(fiber.StatusInternalServerError).SendString("Failed to exchange token")
|
return c.Status(fiber.StatusInternalServerError).SendString("Failed to exchange token")
|
||||||
}
|
}
|
||||||
@ -61,10 +59,10 @@ func githubCallback(c *fiber.Ctx) error {
|
|||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
|
||||||
body, _ := io.ReadAll(resp.Body)
|
data, _ := io.ReadAll(resp.Body)
|
||||||
if resp.StatusCode != 200 {
|
if resp.StatusCode != 200 {
|
||||||
return c.Status(fiber.StatusInternalServerError).
|
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
|
// Parse user info
|
||||||
@ -76,7 +74,7 @@ func githubCallback(c *fiber.Ctx) error {
|
|||||||
Email string `json:"email"`
|
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")
|
return c.Status(fiber.StatusInternalServerError).SendString("Failed to parse user info")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,7 +5,6 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"log"
|
|
||||||
"os"
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
@ -17,61 +16,39 @@ import (
|
|||||||
"rul.sh/vaulterm/server/utils"
|
"rul.sh/vaulterm/server/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
type GitlabCfg struct {
|
var gitlabCfg *oauth2.Config
|
||||||
oauth2.Config
|
|
||||||
verifier string
|
|
||||||
challenge string
|
|
||||||
}
|
|
||||||
|
|
||||||
var gitlabCfg *GitlabCfg
|
func getGitlabConfig() *oauth2.Config {
|
||||||
|
|
||||||
func getGitlabConfig() *GitlabCfg {
|
|
||||||
if gitlabCfg != nil {
|
if gitlabCfg != nil {
|
||||||
return gitlabCfg
|
return gitlabCfg
|
||||||
}
|
}
|
||||||
|
|
||||||
oauthCfg := oauth2.Config{
|
gitlabCfg = &oauth2.Config{
|
||||||
ClientID: os.Getenv("GITLAB_CLIENT_ID"),
|
ClientID: os.Getenv("GITLAB_CLIENT_ID"),
|
||||||
ClientSecret: os.Getenv("GITLAB_CLIENT_SECRET"),
|
ClientSecret: os.Getenv("GITLAB_CLIENT_SECRET"),
|
||||||
Endpoint: gitlab.Endpoint,
|
Endpoint: gitlab.Endpoint,
|
||||||
// RedirectURL: "http://localhost:3000/auth/oauth/gitlab/callback",
|
RedirectURL: "vaulterm://auth/login",
|
||||||
RedirectURL: "http://localhost:8081",
|
Scopes: []string{"read_user"},
|
||||||
Scopes: []string{"read_user"},
|
|
||||||
}
|
|
||||||
verifier := oauth2.GenerateVerifier()
|
|
||||||
challenge := oauth2.S256ChallengeFromVerifier(verifier)
|
|
||||||
|
|
||||||
gitlabCfg = &GitlabCfg{
|
|
||||||
Config: oauthCfg,
|
|
||||||
verifier: verifier,
|
|
||||||
challenge: challenge,
|
|
||||||
}
|
}
|
||||||
return gitlabCfg
|
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 {
|
func gitlabCallback(c *fiber.Ctx) error {
|
||||||
cfg := getGitlabConfig()
|
var body struct {
|
||||||
code := c.Query("code")
|
Code string `json:"code"`
|
||||||
verifier := c.Query("verifier")
|
Verifier string `json:"verifier"`
|
||||||
|
|
||||||
if code == "" {
|
|
||||||
return c.Status(fiber.StatusBadRequest).SendString("Missing code")
|
|
||||||
}
|
}
|
||||||
if verifier == "" {
|
if err := c.BodyParser(&body); err != nil {
|
||||||
verifier = cfg.verifier
|
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
|
// 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 {
|
if err != nil {
|
||||||
log.Println(token, err)
|
|
||||||
return c.Status(fiber.StatusInternalServerError).SendString("Failed to exchange token")
|
return c.Status(fiber.StatusInternalServerError).SendString("Failed to exchange token")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -83,10 +60,10 @@ func gitlabCallback(c *fiber.Ctx) error {
|
|||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
|
||||||
body, _ := io.ReadAll(resp.Body)
|
data, _ := io.ReadAll(resp.Body)
|
||||||
if resp.StatusCode != 200 {
|
if resp.StatusCode != 200 {
|
||||||
return c.Status(fiber.StatusInternalServerError).
|
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
|
// Parse user info
|
||||||
@ -98,7 +75,7 @@ func gitlabCallback(c *fiber.Ctx) error {
|
|||||||
Email string `json:"email"`
|
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")
|
return c.Status(fiber.StatusInternalServerError).SendString("Failed to parse user info")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -16,11 +16,8 @@ func Router(app *fiber.App) {
|
|||||||
router.Post("/register", register)
|
router.Post("/register", register)
|
||||||
router.Post("/logout", middleware.Protected(), logout)
|
router.Post("/logout", middleware.Protected(), logout)
|
||||||
|
|
||||||
oauth := router.Group("/oauth")
|
router.Post("/github", githubCallback)
|
||||||
oauth.Get("/github", githubRedir)
|
router.Post("/gitlab", gitlabCallback)
|
||||||
oauth.Get("/github/callback", githubCallback)
|
|
||||||
oauth.Get("/gitlab", gitlabRedir)
|
|
||||||
oauth.Get("/gitlab/callback", gitlabCallback)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func login(c *fiber.Ctx) error {
|
func login(c *fiber.Ctx) error {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user