Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,7 @@ drive-service-account.json
# Go workspace file
go.work

coverage.html
coverage.html

jwtRS256.key
jwtRS256.key.pub
14 changes: 12 additions & 2 deletions config/config.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
package config

import "os"
import (
"crypto/rsa"
"os"
)

var Version = "3.2.13"
var Version = "3.3.0"
var Env = os.Getenv("ENV")
var Port = os.Getenv("PORT")
var Prefix = os.Getenv("PREFIX")
Expand Down Expand Up @@ -38,6 +41,13 @@ var SubteamRoleNames = []string{"Aero", "Business", "Chassis", "Data", "Electron

var AuthSigningKey = os.Getenv("AUTH_SIGNING_KEY")

var RsaPublicKey *rsa.PublicKey
var RsaPrivateKey *rsa.PrivateKey
var RsaPublicKeyJWKS map[string]interface{}

var RsaPublicKeyString = os.Getenv("RSA_PUBLIC_KEY")
var RsaPrivateKeyString = os.Getenv("RSA_PRIVATE_KEY")

var MemberDirectorySheetID = "1reuLZox2daj8r2H-lZrwB4oFPYlJ6oC7983UUaZd6AY"

var DriveCron = os.Getenv("DRIVE_CRON")
Expand Down
5 changes: 5 additions & 0 deletions controller/auth_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,18 @@ package controller

import (
"net/http"
"sentinel/config"
"sentinel/model"
"sentinel/service"
"sentinel/utils"

"github.com/gin-gonic/gin"
)

func GetJWKS(c *gin.Context) {
c.JSON(http.StatusOK, config.RsaPublicKeyJWKS)
}

func RegisterAccountPassword(c *gin.Context) {
RequireAny(c, RequestTokenHasScope(c, "sentinel:all"))

Expand Down
1 change: 1 addition & 0 deletions controller/route_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ func SetupRouter() *gin.Engine {

func InitializeRoutes(router *gin.Engine) {
router.GET("/ping", Ping)
router.GET("/auth/keys.json", GetJWKS)
router.POST("/auth/register", RegisterAccountPassword)
router.POST("/auth/login", LoginAccount)
router.POST("/auth/login/discord", LoginDiscord)
Expand Down
2 changes: 1 addition & 1 deletion main.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ func main() {
defer utils.Logger.Sync()

database.InitializeDB()
service.InitializeKeys()
service.InitializeDrive()
service.ConnectDiscord()
service.InitializeRoles()
Expand All @@ -25,7 +26,6 @@ func main() {
controller.RegisterDriveCronJob()
controller.RegisteGithubCronJob()
controller.RegisterWikiCronJob()
// service.FindAllNonVerifiedUsers()

router := controller.SetupRouter()
controller.InitializeRoutes(router)
Expand Down
3 changes: 3 additions & 0 deletions model/oauth.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ import (
)

var ValidOauthScopes = map[string]string{
"openid": "OpenID Connect scope",
"profile": "OIDC profile scope",
"email": "OIDC email scope",
"user:read": "Read user account information",
"user:write": "Edit user account information",
"drive:read": "Read user's team drive access information",
Expand Down
10 changes: 10 additions & 0 deletions scripts/keygen.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#!/bin/bash

ssh-keygen -t rsa -b 4096 -m PEM -f jwtRS256.key
openssl rsa -in jwtRS256.key -pubout -outform PEM -out jwtRS256.key.pub

echo "RSA_PUBLIC_KEY=\"$(awk 'NF {sub(/\r/, ""); printf "%s\\n",$0;}' jwtRS256.key.pub)\""
echo "RSA_PRIVATE_KEY=\"$(awk 'NF {sub(/\r/, ""); printf "%s\\n",$0;}' jwtRS256.key)\""

# Clean up temporary files
rm jwtRS256.key jwtRS256.key.pub
45 changes: 41 additions & 4 deletions service/auth_service.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
package service

import (
"crypto/rsa"
"encoding/base64"
"fmt"
"math/big"
"sentinel/config"
"sentinel/database"
"sentinel/model"
Expand All @@ -14,6 +17,40 @@ import (
"golang.org/x/crypto/bcrypt"
)

func InitializeKeys() {
// Parse the RSA public key
publicKey, err := jwt.ParseRSAPublicKeyFromPEM([]byte(config.RsaPublicKeyString))
if err != nil {
utils.SugarLogger.Errorln("Failed to parse RSA public key:", err)
}
config.RsaPublicKey = publicKey
// Parse the RSA private key
privateKey, err := jwt.ParseRSAPrivateKeyFromPEM([]byte(config.RsaPrivateKeyString))
if err != nil {
utils.SugarLogger.Errorln("Failed to parse RSA private key:", err)
}
config.RsaPrivateKey = privateKey
config.RsaPublicKeyJWKS = PublicKeyToJWKS(publicKey)
}

func PublicKeyToJWKS(publicKey *rsa.PublicKey) map[string]interface{} {
e := base64.RawURLEncoding.EncodeToString(big.NewInt(int64(publicKey.E)).Bytes())
n := base64.RawURLEncoding.EncodeToString(publicKey.N.Bytes())

return map[string]interface{}{
"keys": []map[string]interface{}{
{
"kty": "RSA",
"use": "sig",
"alg": "RS256",
"kid": "1",
"n": n,
"e": e,
},
},
}
}

func RegisterEmailPassword(email string, password string) (string, error) {
user := GetUserByEmail(email)
if user.ID == "" {
Expand Down Expand Up @@ -97,15 +134,15 @@ func GenerateJWT(id string, email string, scope string, client_id string) (strin
Scope: scope,
RegisteredClaims: jwt.RegisteredClaims{
ID: id,
Issuer: "sso.gauchoracing.com",
Issuer: "https://sso.gauchoracing.com/",
Audience: jwt.ClaimStrings{client_id},
IssuedAt: jwt.NewNumericDate(time.Now()),
ExpiresAt: jwt.NewNumericDate(expirationTime),
},
}

token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
signedToken, err := token.SignedString([]byte(config.AuthSigningKey))
token := jwt.NewWithClaims(jwt.SigningMethodRS256, claims)
signedToken, err := token.SignedString(config.RsaPrivateKey)
if err != nil {
utils.SugarLogger.Errorln(err.Error())
return "", err
Expand All @@ -116,7 +153,7 @@ func GenerateJWT(id string, email string, scope string, client_id string) (strin
func ValidateJWT(token string) (*model.AuthClaims, error) {
claims := &model.AuthClaims{}
_, err := jwt.ParseWithClaims(token, claims, func(token *jwt.Token) (interface{}, error) {
return []byte(config.AuthSigningKey), nil
return config.RsaPublicKey, nil
})
if err != nil {
utils.SugarLogger.Errorln(err.Error())
Expand Down
10 changes: 9 additions & 1 deletion service/oauth_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -137,11 +137,19 @@ func ValidateScope(scopes string) bool {
func GenerateAuthorizationCode(clientID, userID, scope string) (model.AuthorizationCode, error) {
code := generateCryptoString(8)
expiresAt := time.Now().Add(5 * time.Minute)

// Check if scope contains "oidc" and add "user:read" if it does
scopes := strings.Split(scope, " ")
if contains(scopes, "oidc") && !contains(scopes, "user:read") {
scopes = append(scopes, "user:read")
}
updatedScope := strings.Join(scopes, " ")

authCode := model.AuthorizationCode{
Code: code,
ClientID: clientID,
UserID: userID,
Scope: scope,
Scope: updatedScope,
ExpiresAt: utils.WithPrecision(expiresAt),
}
result := database.DB.Create(&authCode)
Expand Down
13 changes: 12 additions & 1 deletion utils/config.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package utils

import "sentinel/config"
import (
"sentinel/config"
"strings"
)

func VerifyConfig() {
if config.Port == "" {
Expand Down Expand Up @@ -44,6 +47,14 @@ func VerifyConfig() {
if config.AuthSigningKey == "" {
SugarLogger.Errorf("AUTH_SIGNING_KEY is not set")
}
if config.RsaPublicKeyString == "" {
SugarLogger.Errorf("RSA_PUBLIC_KEY is not set")
}
config.RsaPublicKeyString = strings.ReplaceAll(config.RsaPublicKeyString, "\\n", "\n")
if config.RsaPrivateKeyString == "" {
SugarLogger.Errorf("RSA_PRIVATE_KEY is not set")
}
config.RsaPrivateKeyString = strings.ReplaceAll(config.RsaPrivateKeyString, "\\n", "\n")
if config.DriveCron == "" {
config.DriveCron = "0 * * * *"
SugarLogger.Infof("DRIVE_CRON is not set, defaulting to %s", config.DriveCron)
Expand Down