feat: add github oauth

This commit is contained in:
2024-11-17 17:54:41 +07:00
parent c8e61ed4aa
commit a6cc450ea7
22 changed files with 419 additions and 74 deletions
+2 -6
View File
@@ -8,6 +8,7 @@ import (
"github.com/gofiber/fiber/v2/middleware/cors"
"github.com/joho/godotenv"
"rul.sh/vaulterm/server/app/auth"
"rul.sh/vaulterm/server/app/server"
"rul.sh/vaulterm/server/db"
"rul.sh/vaulterm/server/middleware"
"rul.sh/vaulterm/server/utils"
@@ -31,12 +32,7 @@ func NewApp() *fiber.App {
app.Use(cors.New())
// Server info
app.Get("/server", func(c *fiber.Ctx) error {
return c.JSON(&fiber.Map{
"name": "Vaulterm",
"version": "0.0.1",
})
})
server.Router(app)
// Health check
app.Get("/health-check", func(c *fiber.Ctx) error {
+124
View 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,
})
}
+6
View File
@@ -23,6 +23,12 @@ func (r *Auth) FindUser(username string, email string) (*models.User, error) {
return &user, ret.Error
}
func (r *Auth) FindUserAccount(accountType string, accountId string) (*models.UserAccount, error) {
var user models.UserAccount
ret := r.db.Where("type = ? AND account_id = ?", accountType, accountId).Joins("User").First(&user)
return &user, ret.Error
}
func (r *Auth) CreateUserSession(user *models.User) (string, error) {
sessionId, err := lib.GenerateSessionID(20)
if err != nil {
+4
View File
@@ -15,6 +15,10 @@ func Router(app *fiber.App) {
router.Get("/user", middleware.Protected(), getUser)
router.Post("/register", register)
router.Post("/logout", middleware.Protected(), logout)
oauth := router.Group("/oauth")
oauth.Get("/github", githubRedir)
oauth.Get("/github/callback", githubCallback)
}
func login(c *fiber.Ctx) error {
+29
View 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)
}
+1
View File
@@ -7,6 +7,7 @@ import (
var Models = []interface{}{
&models.User{},
&models.UserSession{},
&models.UserAccount{},
&models.Keychain{},
&models.Host{},
&models.Team{},
+19 -1
View File
@@ -5,6 +5,8 @@ import "slices"
const (
UserRoleUser = "user"
UserRoleAdmin = "admin"
UserAccountTypeGithub = "github"
)
type User struct {
@@ -15,8 +17,10 @@ type User struct {
Password string `json:"-"`
Email string `json:"email" gorm:"unique"`
Role string `json:"role" gorm:"default:user;not null;index:users_role_idx;type:varchar(8)"`
Image string `json:"image" gorm:"type:varchar(255)"`
Teams []*TeamMembers `json:"teams" gorm:"foreignKey:UserID"`
Teams []*TeamMembers `json:"teams" gorm:"foreignKey:UserID"`
Accounts []*UserAccount `json:"accounts" gorm:"foreignKey:UserID"`
Timestamps
SoftDeletes
@@ -31,6 +35,20 @@ type UserSession struct {
SoftDeletes
}
type UserAccount struct {
Model
UserID string `json:"userId" gorm:"type:varchar(26)"`
User User `json:"user" gorm:"foreignKey:UserID"`
Type string `json:"type" gorm:"type:varchar(16);index:user_accounts_type_idx"`
AccountID string `json:"accountId" gorm:"type:varchar(64);index:user_accounts_accountid_idx"`
Username string `json:"username" gorm:"type:varchar(64);index:user_accounts_username_idx"`
Email string `json:"email" gorm:"type:varchar(64);index:user_accounts_email_idx"`
Timestamps
}
func (u *User) IsAdmin() bool {
return u.Role == UserRoleAdmin
}