26
.dockerignore
Normal file
26
.dockerignore
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
# Ignore Docker Compose configuration files
|
||||||
|
docker-compose.yaml
|
||||||
|
|
||||||
|
# Ignore development Dockerfile
|
||||||
|
Dockerfile.dev
|
||||||
|
|
||||||
|
# Ignore the data directory
|
||||||
|
data/
|
||||||
|
|
||||||
|
# Ignore version control system directories
|
||||||
|
.git/
|
||||||
|
|
||||||
|
# Ignore log and temporary files
|
||||||
|
*.log
|
||||||
|
*.tmp
|
||||||
|
*.swp
|
||||||
|
|
||||||
|
# Ignore editor/IDE configuration files
|
||||||
|
.vscode/
|
||||||
|
.idea/
|
||||||
|
|
||||||
|
# Ignore binaries and build cache
|
||||||
|
release/
|
||||||
|
bin/
|
||||||
|
*.exe
|
||||||
|
*.out
|
||||||
72
Dockerfile.dev
Normal file
72
Dockerfile.dev
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
# Use build arguments for Go version and architecture
|
||||||
|
ARG GO_VERSION=1.22
|
||||||
|
ARG BUILDARCH=amd64
|
||||||
|
|
||||||
|
# Stage 1: Builder Stage
|
||||||
|
# FROM golang:${GO_VERSION}-alpine AS builder
|
||||||
|
FROM crazymax/xgo:${GO_VERSION} AS builder
|
||||||
|
|
||||||
|
# Set up working directory
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Step 1: Copy the source code
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
# Step 2: Download dependencies
|
||||||
|
RUN go mod tidy && go mod download
|
||||||
|
|
||||||
|
|
||||||
|
# Step 3: Install swag and Run the build script
|
||||||
|
RUN go install github.com/swaggo/swag/cmd/swag@latest && \
|
||||||
|
swag init -g cmd/apimain.go --output docs/api --instanceName api --exclude http/controller/admin && \
|
||||||
|
swag init -g cmd/apimain.go --output docs/admin --instanceName admin --exclude http/controller/api
|
||||||
|
|
||||||
|
# Build the Go application with CGO enabled and specified ldflags
|
||||||
|
RUN CGO_ENABLED=1 GOOS=linux go build -a \
|
||||||
|
-ldflags "-s -w --extldflags '-static -fpic'" \
|
||||||
|
-installsuffix cgo -o release/apimain cmd/apimain.go
|
||||||
|
|
||||||
|
# Stage 2: Frontend Build Stage (builder2)
|
||||||
|
FROM node:18-alpine AS builder2
|
||||||
|
|
||||||
|
# Set working directory
|
||||||
|
WORKDIR /frontend
|
||||||
|
|
||||||
|
RUN apk update && apk add git --no-cache
|
||||||
|
|
||||||
|
# Clone the frontend repository
|
||||||
|
RUN git clone https://github.com/lejianwen/rustdesk-api-web .
|
||||||
|
|
||||||
|
# Install npm dependencies and build the frontend
|
||||||
|
RUN npm install && npm run build
|
||||||
|
|
||||||
|
# Stage 2: Final Image
|
||||||
|
FROM alpine:latest
|
||||||
|
|
||||||
|
# Set up working directory
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Install necessary runtime dependencies
|
||||||
|
RUN apk add --no-cache tzdata file
|
||||||
|
|
||||||
|
# Copy the built application and resources from the builder stage
|
||||||
|
COPY --from=builder /app/release /app/
|
||||||
|
COPY --from=builder /app/conf /app/conf/
|
||||||
|
COPY --from=builder /app/resources /app/resources/
|
||||||
|
COPY --from=builder /app/docs /app/docs/
|
||||||
|
# Copy frontend build from builder2 stage
|
||||||
|
COPY --from=builder2 /frontend/dist/ /app/resources/admin/
|
||||||
|
|
||||||
|
# Ensure the binary is correctly built and linked
|
||||||
|
RUN file /app/apimain && \
|
||||||
|
mkdir -p /app/data && \
|
||||||
|
mkdir -p /app/runtime
|
||||||
|
|
||||||
|
# Set up a volume for persistent data
|
||||||
|
VOLUME /app/data
|
||||||
|
|
||||||
|
# Expose the necessary port
|
||||||
|
EXPOSE 21114
|
||||||
|
|
||||||
|
# Define the command to run the application
|
||||||
|
CMD ["./apimain"]
|
||||||
38
build.sh
Normal file → Executable file
38
build.sh
Normal file → Executable file
@@ -1,16 +1,46 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
|
|
||||||
rm release -rf
|
set -e
|
||||||
|
# Automatically get the current environment's GOARCH; if not defined, use the detected system architecture
|
||||||
|
GOARCH=${GOARCH:-$(go env GOARCH)}
|
||||||
|
DOCS="true"
|
||||||
|
# Safely remove the old release directory
|
||||||
|
rm -rf release
|
||||||
|
|
||||||
|
# Set Go environment variables
|
||||||
go env -w GO111MODULE=on
|
go env -w GO111MODULE=on
|
||||||
go env -w GOPROXY=https://goproxy.cn,direct
|
go env -w GOPROXY=https://goproxy.cn,direct
|
||||||
go env -w CGO_ENABLED=1
|
go env -w CGO_ENABLED=1
|
||||||
go env -w GOOS=linux
|
go env -w GOOS=linux
|
||||||
go env -w GOARCH=amd64
|
go env -w GOARCH=${GOARCH}
|
||||||
swag init -g cmd/apimain.go --output docs/api --instanceName api --exclude http/controller/admin
|
|
||||||
swag init -g cmd/apimain.go --output docs/admin --instanceName admin --exclude http/controller/api
|
|
||||||
|
# Generate Swagger documentation if DOCS is not empty
|
||||||
|
if [ -n "${DOCS}" ]; then
|
||||||
|
# Check if swag is installed
|
||||||
|
if ! command -v swag &> /dev/null; then
|
||||||
|
echo "swag command not found. Please install it using:"
|
||||||
|
echo "go install github.com/swaggo/swag/cmd/swag@latest"
|
||||||
|
echo "Skipping Swagger documentation generation due to missing swag tool."
|
||||||
|
else
|
||||||
|
echo "Generating Swagger documentation..."
|
||||||
|
swag init -g cmd/apimain.go --output docs/api --instanceName api --exclude http/controller/admin
|
||||||
|
swag init -g cmd/apimain.go --output docs/admin --instanceName admin --exclude http/controller/api
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "Skipping Swagger documentation generation due to DOCS is empty."
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Compile the Go code and output it to the release directory
|
||||||
go build -o release/apimain cmd/apimain.go
|
go build -o release/apimain cmd/apimain.go
|
||||||
|
|
||||||
|
# Copy resource files to the release directory
|
||||||
cp -ar resources release/
|
cp -ar resources release/
|
||||||
cp -ar docs release/
|
cp -ar docs release/
|
||||||
cp -ar conf release/
|
cp -ar conf release/
|
||||||
|
|
||||||
|
# Create necessary directory structures
|
||||||
mkdir -p release/data
|
mkdir -p release/data
|
||||||
mkdir -p release/runtime
|
mkdir -p release/runtime
|
||||||
|
|
||||||
|
echo "Build and setup completed successfully."
|
||||||
@@ -11,3 +11,10 @@ type GoogleOauth struct {
|
|||||||
ClientSecret string `mapstructure:"client-secret"`
|
ClientSecret string `mapstructure:"client-secret"`
|
||||||
RedirectUrl string `mapstructure:"redirect-url"`
|
RedirectUrl string `mapstructure:"redirect-url"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type OidcOauth struct {
|
||||||
|
Issuer string `mapstructure:"issuer"`
|
||||||
|
ClientId string `mapstructure:"client-id"`
|
||||||
|
ClientSecret string `mapstructure:"client-secret"`
|
||||||
|
RedirectUrl string `mapstructure:"redirect-url"`
|
||||||
|
}
|
||||||
@@ -1,15 +1,20 @@
|
|||||||
services:
|
services:
|
||||||
rustdesk-api:
|
rustdesk-api:
|
||||||
image: lejianwen/rustdesk-api
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: Dockerfile.dev
|
||||||
|
# image: lejianwen/rustdesk-api
|
||||||
container_name: rustdesk-api
|
container_name: rustdesk-api
|
||||||
environment:
|
environment:
|
||||||
- TZ=Asia/Shanghai
|
- TZ=Asia/Shanghai
|
||||||
- RUSTDESK_API_RUSTDESK_ID_SERVER=192.168.1.66:21116
|
- RUSTDESK_API_RUSTDESK_ID_SERVER=192.168.1.66:21116
|
||||||
- RUSTDESK_API_RUSTDESK_RELAY_SERVER=192.168.1.66:21117
|
- RUSTDESK_API_RUSTDESK_RELAY_SERVER=192.168.1.66:21117
|
||||||
- RUSTDESK_API_RUSTDESK_API_SERVER=http://192.168.1.66:21114
|
- RUSTDESK_API_RUSTDESK_API_SERVER=http://127.0.0.1:21114
|
||||||
- RUSTDESK_API_RUSTDESK_KEY=123456789
|
- RUSTDESK_API_RUSTDESK_KEY=123456789
|
||||||
ports:
|
ports:
|
||||||
- 21114:21114
|
- 21114:21114
|
||||||
volumes:
|
volumes:
|
||||||
- /data/rustdesk/api:/app/data #将数据库挂载出来方便备份
|
- ./data/rustdesk/api:/app/data #将数据库挂载出来方便备份
|
||||||
|
- ./conf:/app/conf # config
|
||||||
|
# - ./resources:/app/resources # 静态资源
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
@@ -140,6 +140,13 @@ func (o *Oauth) Unbind(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if f.Op == model.OauthTypeOidc {
|
||||||
|
err = service.AllService.OauthService.UnBindOidcUser(u.Id)
|
||||||
|
if err != nil {
|
||||||
|
response.Fail(c, 101, response.TranslateMsg(c, "OperationFailed")+err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
response.Success(c, nil)
|
response.Success(c, nil)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -92,6 +92,10 @@ func (l *Login) LoginOptions(c *gin.Context) {
|
|||||||
if err == nil {
|
if err == nil {
|
||||||
oauthOks = append(oauthOks, model.OauthTypeGoogle)
|
oauthOks = append(oauthOks, model.OauthTypeGoogle)
|
||||||
}
|
}
|
||||||
|
err, _ = service.AllService.OauthService.GetOauthConfig(model.OauthTypeOidc)
|
||||||
|
if err == nil {
|
||||||
|
oauthOks = append(oauthOks, model.OauthTypeOidc)
|
||||||
|
}
|
||||||
oauthOks = append(oauthOks, model.OauthTypeWebauth)
|
oauthOks = append(oauthOks, model.OauthTypeWebauth)
|
||||||
var oidcItems []map[string]string
|
var oidcItems []map[string]string
|
||||||
for _, v := range oauthOks {
|
for _, v := range oauthOks {
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ func (o *Oauth) OidcAuth(c *gin.Context) {
|
|||||||
response.Error(c, response.TranslateMsg(c, "ParamsError")+err.Error())
|
response.Error(c, response.TranslateMsg(c, "ParamsError")+err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if f.Op != model.OauthTypeWebauth && f.Op != model.OauthTypeGoogle && f.Op != model.OauthTypeGithub {
|
if f.Op != model.OauthTypeWebauth && f.Op != model.OauthTypeGoogle && f.Op != model.OauthTypeGithub && f.Op != model.OauthTypeOidc {
|
||||||
response.Error(c, response.TranslateMsg(c, "ParamsError"))
|
response.Error(c, response.TranslateMsg(c, "ParamsError"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -254,6 +254,70 @@ func (o *Oauth) OauthCallback(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if ty == model.OauthTypeOidc {
|
||||||
|
code := c.Query("code")
|
||||||
|
err, userData := service.AllService.OauthService.OidcCallback(code)
|
||||||
|
if err != nil {
|
||||||
|
c.String(http.StatusInternalServerError, response.TranslateMsg(c, "OauthFailed")+response.TranslateMsg(c, err.Error()))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
//将空格替换成_
|
||||||
|
// OidcName := strings.Replace(userData.Name, " ", "_", -1)
|
||||||
|
if ac == service.OauthActionTypeBind {
|
||||||
|
//fmt.Println("bind", ty, userData)
|
||||||
|
utr := service.AllService.OauthService.UserThirdInfo(ty, userData.Sub)
|
||||||
|
if utr.UserId > 0 {
|
||||||
|
c.String(http.StatusInternalServerError, response.TranslateMsg(c, "OauthHasBindOtherUser"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
//绑定
|
||||||
|
u := service.AllService.UserService.InfoById(v.UserId)
|
||||||
|
if u == nil {
|
||||||
|
c.String(http.StatusInternalServerError, response.TranslateMsg(c, "ItemNotFound"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
//绑定, user preffered_username as username
|
||||||
|
err = service.AllService.OauthService.BindOidcUser(userData.Sub, userData.PreferredUsername, v.UserId)
|
||||||
|
if err != nil {
|
||||||
|
c.String(http.StatusInternalServerError, response.TranslateMsg(c, "BindFail"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.String(http.StatusOK, response.TranslateMsg(c, "BindSuccess"))
|
||||||
|
return
|
||||||
|
} else if ac == service.OauthActionTypeLogin {
|
||||||
|
if v.UserId != 0 {
|
||||||
|
c.String(http.StatusInternalServerError, response.TranslateMsg(c, "OauthHasBeenSuccess"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
u := service.AllService.UserService.InfoByOidcSub(userData.Sub)
|
||||||
|
if u == nil {
|
||||||
|
oa := service.AllService.OauthService.InfoByOp(ty)
|
||||||
|
if !*oa.AutoRegister {
|
||||||
|
//c.String(http.StatusInternalServerError, "还未绑定用户,请先绑定")
|
||||||
|
|
||||||
|
v.ThirdName = userData.PreferredUsername
|
||||||
|
v.ThirdOpenId = userData.Sub
|
||||||
|
v.ThirdEmail = userData.Email
|
||||||
|
url := global.Config.Rustdesk.ApiServer + "/_admin/#/oauth/bind/" + cacheKey
|
||||||
|
c.Redirect(http.StatusFound, url)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//自动注册
|
||||||
|
u = service.AllService.UserService.RegisterByOidc(userData.PreferredUsername, userData.Sub)
|
||||||
|
if u.Id == 0 {
|
||||||
|
c.String(http.StatusInternalServerError, response.TranslateMsg(c, "OauthRegisterFailed"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
v.UserId = u.Id
|
||||||
|
service.AllService.OauthService.SetOauthCache(cacheKey, v, 0)
|
||||||
|
c.String(http.StatusOK, response.TranslateMsg(c, "OauthSuccess"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
c.String(http.StatusInternalServerError, response.TranslateMsg(c, "SystemError"))
|
c.String(http.StatusInternalServerError, response.TranslateMsg(c, "SystemError"))
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,8 @@ type UnBindOauthForm struct {
|
|||||||
type OauthForm struct {
|
type OauthForm struct {
|
||||||
Id uint `json:"id"`
|
Id uint `json:"id"`
|
||||||
Op string `json:"op" validate:"required"`
|
Op string `json:"op" validate:"required"`
|
||||||
|
Issuer string `json:"issuer" validate:"omitempty,url"`
|
||||||
|
Scopes string `json:"scopes" validate:"omitempty"`
|
||||||
ClientId string `json:"client_id" validate:"required"`
|
ClientId string `json:"client_id" validate:"required"`
|
||||||
ClientSecret string `json:"client_secret" validate:"required"`
|
ClientSecret string `json:"client_secret" validate:"required"`
|
||||||
RedirectUrl string `json:"redirect_url" validate:"required"`
|
RedirectUrl string `json:"redirect_url" validate:"required"`
|
||||||
@@ -28,6 +30,8 @@ func (of *OauthForm) ToOauth() *model.Oauth {
|
|||||||
ClientSecret: of.ClientSecret,
|
ClientSecret: of.ClientSecret,
|
||||||
RedirectUrl: of.RedirectUrl,
|
RedirectUrl: of.RedirectUrl,
|
||||||
AutoRegister: of.AutoRegister,
|
AutoRegister: of.AutoRegister,
|
||||||
|
Issuer: of.Issuer,
|
||||||
|
Scopes: of.Scopes,
|
||||||
}
|
}
|
||||||
oa.Id = of.Id
|
oa.Id = of.Id
|
||||||
return oa
|
return oa
|
||||||
|
|||||||
@@ -7,12 +7,15 @@ type Oauth struct {
|
|||||||
ClientSecret string `json:"client_secret"`
|
ClientSecret string `json:"client_secret"`
|
||||||
RedirectUrl string `json:"redirect_url"`
|
RedirectUrl string `json:"redirect_url"`
|
||||||
AutoRegister *bool `json:"auto_register"`
|
AutoRegister *bool `json:"auto_register"`
|
||||||
|
Scopes string `json:"scopes"`
|
||||||
|
Issuer string `json:"issuer"`
|
||||||
TimeModel
|
TimeModel
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
OauthTypeGithub = "github"
|
OauthTypeGithub = "github"
|
||||||
OauthTypeGoogle = "google"
|
OauthTypeGoogle = "google"
|
||||||
|
OauthTypeOidc = "oidc"
|
||||||
OauthTypeWebauth = "webauth"
|
OauthTypeWebauth = "webauth"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
191
service/oauth.go
191
service/oauth.go
@@ -17,8 +17,17 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Define a struct to parse the .well-known/openid-configuration response
|
||||||
|
type OidcEndpoint struct {
|
||||||
|
Issuer string `json:"issuer"`
|
||||||
|
AuthURL string `json:"authorization_endpoint"`
|
||||||
|
TokenURL string `json:"token_endpoint"`
|
||||||
|
UserInfo string `json:"userinfo_endpoint"`
|
||||||
|
}
|
||||||
|
|
||||||
type OauthService struct {
|
type OauthService struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -78,6 +87,14 @@ type GoogleUserdata struct {
|
|||||||
Picture string `json:"picture"`
|
Picture string `json:"picture"`
|
||||||
VerifiedEmail bool `json:"verified_email"`
|
VerifiedEmail bool `json:"verified_email"`
|
||||||
}
|
}
|
||||||
|
type OidcUserdata struct {
|
||||||
|
Sub string `json:"sub"`
|
||||||
|
Email string `json:"email"`
|
||||||
|
VerifiedEmail bool `json:"email_verified"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
PreferredUsername string `json:"preferred_username"`
|
||||||
|
}
|
||||||
|
|
||||||
type OauthCacheItem struct {
|
type OauthCacheItem struct {
|
||||||
UserId uint `json:"user_id"`
|
UserId uint `json:"user_id"`
|
||||||
Id string `json:"id"` //rustdesk的设备ID
|
Id string `json:"id"` //rustdesk的设备ID
|
||||||
@@ -137,35 +154,102 @@ func (os *OauthService) BeginAuth(op string) (error error, code, url string) {
|
|||||||
return err, code, ""
|
return err, code, ""
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetOauthConfig 获取配置
|
// Method to fetch OIDC configuration dynamically
|
||||||
|
func FetchOidcConfig(issuer string) (error, OidcEndpoint) {
|
||||||
|
configURL := strings.TrimSuffix(issuer, "/") + "/.well-known/openid-configuration"
|
||||||
|
|
||||||
|
// Get the HTTP client (with or without proxy based on configuration)
|
||||||
|
client := getHTTPClientWithProxy()
|
||||||
|
|
||||||
|
resp, err := client.Get(configURL)
|
||||||
|
if err != nil {
|
||||||
|
return errors.New("failed to fetch OIDC configuration"), OidcEndpoint{}
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
return errors.New("OIDC configuration not found, status code: %d"), OidcEndpoint{}
|
||||||
|
}
|
||||||
|
|
||||||
|
var endpoint OidcEndpoint
|
||||||
|
if err := json.NewDecoder(resp.Body).Decode(&endpoint); err != nil {
|
||||||
|
return errors.New("failed to parse OIDC configuration"), OidcEndpoint{}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, endpoint
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetOauthConfig retrieves the OAuth2 configuration based on the provider type
|
||||||
func (os *OauthService) GetOauthConfig(op string) (error, *oauth2.Config) {
|
func (os *OauthService) GetOauthConfig(op string) (error, *oauth2.Config) {
|
||||||
if op == model.OauthTypeGithub {
|
switch op {
|
||||||
g := os.InfoByOp(model.OauthTypeGithub)
|
case model.OauthTypeGithub:
|
||||||
if g.Id == 0 || g.ClientId == "" || g.ClientSecret == "" || g.RedirectUrl == "" {
|
return os.getGithubConfig()
|
||||||
return errors.New("ConfigNotFound"), nil
|
case model.OauthTypeGoogle:
|
||||||
}
|
return os.getGoogleConfig()
|
||||||
return nil, &oauth2.Config{
|
case model.OauthTypeOidc:
|
||||||
ClientID: g.ClientId,
|
return os.getOidcConfig()
|
||||||
ClientSecret: g.ClientSecret,
|
default:
|
||||||
RedirectURL: g.RedirectUrl,
|
return errors.New("unsupported OAuth type"), nil
|
||||||
Endpoint: github.Endpoint,
|
|
||||||
Scopes: []string{"read:user", "user:email"},
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if op == model.OauthTypeGoogle {
|
}
|
||||||
g := os.InfoByOp(model.OauthTypeGoogle)
|
|
||||||
if g.Id == 0 || g.ClientId == "" || g.ClientSecret == "" || g.RedirectUrl == "" {
|
// Helper function to get GitHub OAuth2 configuration
|
||||||
return errors.New("ConfigNotFound"), nil
|
func (os *OauthService) getGithubConfig() (error, *oauth2.Config) {
|
||||||
}
|
g := os.InfoByOp(model.OauthTypeGithub)
|
||||||
return nil, &oauth2.Config{
|
if g.Id == 0 || g.ClientId == "" || g.ClientSecret == "" || g.RedirectUrl == "" {
|
||||||
ClientID: g.ClientId,
|
return errors.New("ConfigNotFound"), nil
|
||||||
ClientSecret: g.ClientSecret,
|
}
|
||||||
RedirectURL: g.RedirectUrl,
|
return nil, &oauth2.Config{
|
||||||
Endpoint: google.Endpoint,
|
ClientID: g.ClientId,
|
||||||
Scopes: []string{"https://www.googleapis.com/auth/userinfo.profile", "https://www.googleapis.com/auth/userinfo.email"},
|
ClientSecret: g.ClientSecret,
|
||||||
}
|
RedirectURL: g.RedirectUrl,
|
||||||
|
Endpoint: github.Endpoint,
|
||||||
|
Scopes: []string{"read:user", "user:email"},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper function to get Google OAuth2 configuration
|
||||||
|
func (os *OauthService) getGoogleConfig() (error, *oauth2.Config) {
|
||||||
|
g := os.InfoByOp(model.OauthTypeGoogle)
|
||||||
|
if g.Id == 0 || g.ClientId == "" || g.ClientSecret == "" || g.RedirectUrl == "" {
|
||||||
|
return errors.New("ConfigNotFound"), nil
|
||||||
|
}
|
||||||
|
return nil, &oauth2.Config{
|
||||||
|
ClientID: g.ClientId,
|
||||||
|
ClientSecret: g.ClientSecret,
|
||||||
|
RedirectURL: g.RedirectUrl,
|
||||||
|
Endpoint: google.Endpoint,
|
||||||
|
Scopes: []string{"https://www.googleapis.com/auth/userinfo.profile", "https://www.googleapis.com/auth/userinfo.email"},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper function to get OIDC OAuth2 configuration
|
||||||
|
func (os *OauthService) getOidcConfig() (error, *oauth2.Config) {
|
||||||
|
g := os.InfoByOp(model.OauthTypeOidc)
|
||||||
|
if g.Id == 0 || g.ClientId == "" || g.ClientSecret == "" || g.RedirectUrl == "" || g.Issuer == "" {
|
||||||
|
return errors.New("ConfigNotFound"), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set scopes
|
||||||
|
scopes := strings.TrimSpace(g.Scopes)
|
||||||
|
if scopes == "" {
|
||||||
|
scopes = "openid,profile,email"
|
||||||
|
}
|
||||||
|
scopeList := strings.Split(scopes, ",")
|
||||||
|
err, endpoint := FetchOidcConfig(g.Issuer)
|
||||||
|
if err != nil {
|
||||||
|
return err, nil
|
||||||
|
}
|
||||||
|
return nil, &oauth2.Config{
|
||||||
|
ClientID: g.ClientId,
|
||||||
|
ClientSecret: g.ClientSecret,
|
||||||
|
RedirectURL: g.RedirectUrl,
|
||||||
|
Endpoint: oauth2.Endpoint{
|
||||||
|
AuthURL: endpoint.AuthURL,
|
||||||
|
TokenURL: endpoint.TokenURL,
|
||||||
|
},
|
||||||
|
Scopes: scopeList,
|
||||||
}
|
}
|
||||||
return errors.New("ConfigNotFound"), nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func getHTTPClientWithProxy() *http.Client {
|
func getHTTPClientWithProxy() *http.Client {
|
||||||
@@ -269,6 +353,53 @@ func (os *OauthService) GoogleCallback(code string) (error error, userData *Goog
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (os *OauthService) OidcCallback(code string) (error error, userData *OidcUserdata) {
|
||||||
|
err, oauthConfig := os.GetOauthConfig(model.OauthTypeOidc)
|
||||||
|
if err != nil {
|
||||||
|
return err, nil
|
||||||
|
}
|
||||||
|
// 使用代理配置创建 HTTP 客户端
|
||||||
|
httpClient := getHTTPClientWithProxy()
|
||||||
|
ctx := context.WithValue(context.Background(), oauth2.HTTPClient, httpClient)
|
||||||
|
|
||||||
|
token, err := oauthConfig.Exchange(ctx, code)
|
||||||
|
if err != nil {
|
||||||
|
global.Logger.Warn("oauthConfig.Exchange() failed: ", err)
|
||||||
|
error = errors.New("GetOauthTokenError")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用带有代理的 HTTP 客户端获取用户信息
|
||||||
|
client := oauthConfig.Client(ctx, token)
|
||||||
|
g := os.InfoByOp(model.OauthTypeOidc)
|
||||||
|
err, endpoint := FetchOidcConfig(g.Issuer)
|
||||||
|
if err != nil {
|
||||||
|
global.Logger.Warn("failed fetching OIDC configuration: ", err)
|
||||||
|
error = errors.New("FetchOidcConfigError")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
resp, err := client.Get(endpoint.UserInfo)
|
||||||
|
if err != nil {
|
||||||
|
global.Logger.Warn("failed getting user info: ", err)
|
||||||
|
error = errors.New("GetOauthUserInfoError")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer func(Body io.ReadCloser) {
|
||||||
|
err := Body.Close()
|
||||||
|
if err != nil {
|
||||||
|
global.Logger.Warn("failed closing response body: ", err)
|
||||||
|
}
|
||||||
|
}(resp.Body)
|
||||||
|
|
||||||
|
// 解析用户信息
|
||||||
|
if err = json.NewDecoder(resp.Body).Decode(&userData); err != nil {
|
||||||
|
global.Logger.Warn("failed decoding user info: ", err)
|
||||||
|
error = errors.New("DecodeOauthUserInfoError")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
func (os *OauthService) UserThirdInfo(op, openid string) *model.UserThird {
|
func (os *OauthService) UserThirdInfo(op, openid string) *model.UserThird {
|
||||||
ut := &model.UserThird{}
|
ut := &model.UserThird{}
|
||||||
global.DB.Where("open_id = ? and third_type = ?", openid, op).First(ut)
|
global.DB.Where("open_id = ? and third_type = ?", openid, op).First(ut)
|
||||||
@@ -282,6 +413,11 @@ func (os *OauthService) BindGithubUser(openid, username string, userId uint) err
|
|||||||
func (os *OauthService) BindGoogleUser(email, username string, userId uint) error {
|
func (os *OauthService) BindGoogleUser(email, username string, userId uint) error {
|
||||||
return os.BindOauthUser(model.OauthTypeGoogle, email, username, userId)
|
return os.BindOauthUser(model.OauthTypeGoogle, email, username, userId)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (os *OauthService) BindOidcUser(sub, username string, userId uint) error {
|
||||||
|
return os.BindOauthUser(model.OauthTypeOidc, sub, username, userId)
|
||||||
|
}
|
||||||
|
|
||||||
func (os *OauthService) BindOauthUser(thirdType, openid, username string, userId uint) error {
|
func (os *OauthService) BindOauthUser(thirdType, openid, username string, userId uint) error {
|
||||||
utr := &model.UserThird{
|
utr := &model.UserThird{
|
||||||
OpenId: openid,
|
OpenId: openid,
|
||||||
@@ -298,6 +434,9 @@ func (os *OauthService) UnBindGithubUser(userid uint) error {
|
|||||||
func (os *OauthService) UnBindGoogleUser(userid uint) error {
|
func (os *OauthService) UnBindGoogleUser(userid uint) error {
|
||||||
return os.UnBindThird(model.OauthTypeGoogle, userid)
|
return os.UnBindThird(model.OauthTypeGoogle, userid)
|
||||||
}
|
}
|
||||||
|
func (os *OauthService) UnBindOidcUser(userid uint) error {
|
||||||
|
return os.UnBindThird(model.OauthTypeOidc, userid)
|
||||||
|
}
|
||||||
func (os *OauthService) UnBindThird(thirdType string, userid uint) error {
|
func (os *OauthService) UnBindThird(thirdType string, userid uint) error {
|
||||||
return global.DB.Where("user_id = ? and third_type = ?", userid, thirdType).Delete(&model.UserThird{}).Error
|
return global.DB.Where("user_id = ? and third_type = ?", userid, thirdType).Delete(&model.UserThird{}).Error
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -196,6 +196,11 @@ func (us *UserService) InfoByGoogleEmail(email string) *model.User {
|
|||||||
return us.InfoByOauthId(model.OauthTypeGithub, email)
|
return us.InfoByOauthId(model.OauthTypeGithub, email)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// InfoByOidcSub 根据oidc取用户信息
|
||||||
|
func (us *UserService) InfoByOidcSub(sub string) *model.User {
|
||||||
|
return us.InfoByOauthId(model.OauthTypeOidc, sub)
|
||||||
|
}
|
||||||
|
|
||||||
// InfoByOauthId 根据oauth取用户信息
|
// InfoByOauthId 根据oauth取用户信息
|
||||||
func (us *UserService) InfoByOauthId(thirdType, uid string) *model.User {
|
func (us *UserService) InfoByOauthId(thirdType, uid string) *model.User {
|
||||||
ut := AllService.OauthService.UserThirdInfo(thirdType, uid)
|
ut := AllService.OauthService.UserThirdInfo(thirdType, uid)
|
||||||
@@ -219,6 +224,11 @@ func (us *UserService) RegisterByGoogle(name string, email string) *model.User {
|
|||||||
return us.RegisterByOauth(model.OauthTypeGoogle, name, email)
|
return us.RegisterByOauth(model.OauthTypeGoogle, name, email)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RegisterByOidc 注册, use PreferredUsername as username, sub as openid
|
||||||
|
func (us *UserService) RegisterByOidc(PreferredUsername string, sub string) *model.User {
|
||||||
|
return us.RegisterByOauth(model.OauthTypeOidc, PreferredUsername, sub)
|
||||||
|
}
|
||||||
|
|
||||||
// RegisterByOauth 注册
|
// RegisterByOauth 注册
|
||||||
func (us *UserService) RegisterByOauth(thirdType, thirdName, uid string) *model.User {
|
func (us *UserService) RegisterByOauth(thirdType, thirdName, uid string) *model.User {
|
||||||
global.Lock.Lock("registerByOauth")
|
global.Lock.Lock("registerByOauth")
|
||||||
|
|||||||
Reference in New Issue
Block a user