Просмотр исходного кода

Merge pull request #36 from IamTaoChen/oidc-for-web

OIDC for web
1 год назад
Родитель
Сommit
122b3baf6f
4 измененных файлов с 189 добавлено и 55 удалено
  1. 7 7
      Dockerfile.dev
  2. 86 1
      http/controller/admin/login.go
  3. 93 46
      http/controller/api/ouath.go
  4. 3 1
      http/router/admin.go

+ 7 - 7
Dockerfile.dev

@@ -4,7 +4,7 @@ ARG BUILDARCH=amd64
4
 
4
 
5
 # Stage 1: Builder Stage
5
 # Stage 1: Builder Stage
6
 # FROM golang:${GO_VERSION}-alpine AS builder
6
 # FROM golang:${GO_VERSION}-alpine AS builder
7
-FROM crazymax/xgo:${GO_VERSION} AS builder
7
+FROM crazymax/xgo:${GO_VERSION} AS builder-backend
8
 
8
 
9
 # Set up working directory
9
 # Set up working directory
10
 WORKDIR /app
10
 WORKDIR /app
@@ -27,7 +27,7 @@ RUN CGO_ENABLED=1 GOOS=linux go build -a \
27
     -installsuffix cgo -o release/apimain cmd/apimain.go
27
     -installsuffix cgo -o release/apimain cmd/apimain.go
28
 
28
 
29
 # Stage 2: Frontend Build Stage (builder2)
29
 # Stage 2: Frontend Build Stage (builder2)
30
-FROM node:18-alpine AS builder2
30
+FROM node:18-alpine AS builder-admin-frontend
31
 
31
 
32
 # Set working directory
32
 # Set working directory
33
 WORKDIR /frontend
33
 WORKDIR /frontend
@@ -50,12 +50,12 @@ WORKDIR /app
50
 RUN apk add --no-cache tzdata file
50
 RUN apk add --no-cache tzdata file
51
 
51
 
52
 # Copy the built application and resources from the builder stage
52
 # Copy the built application and resources from the builder stage
53
-COPY --from=builder /app/release /app/
54
-COPY --from=builder /app/conf /app/conf/
55
-COPY --from=builder /app/resources /app/resources/
56
-COPY --from=builder /app/docs /app/docs/
53
+COPY --from=builder-backend /app/release /app/
54
+COPY --from=builder-backend /app/conf /app/conf/
55
+COPY --from=builder-backend /app/resources /app/resources/
56
+COPY --from=builder-backend /app/docs /app/docs/
57
 # Copy frontend build from builder2 stage
57
 # Copy frontend build from builder2 stage
58
-COPY --from=builder2 /frontend/dist/ /app/resources/admin/
58
+COPY --from=builder-admin-frontend /frontend/dist/ /app/resources/admin/
59
 
59
 
60
 # Ensure the binary is correctly built and linked
60
 # Ensure the binary is correctly built and linked
61
 RUN file /app/apimain && \
61
 RUN file /app/apimain && \

+ 86 - 1
http/controller/admin/login.go

@@ -5,6 +5,8 @@ import (
5
 	"Gwen/http/request/admin"
5
 	"Gwen/http/request/admin"
6
 	"Gwen/http/response"
6
 	"Gwen/http/response"
7
 	adResp "Gwen/http/response/admin"
7
 	adResp "Gwen/http/response/admin"
8
+	apiReq "Gwen/http/request/api"
9
+	"Gwen/http/controller/api"
8
 	"Gwen/model"
10
 	"Gwen/model"
9
 	"Gwen/service"
11
 	"Gwen/service"
10
 	"fmt"
12
 	"fmt"
@@ -51,7 +53,7 @@ func (ct *Login) Login(c *gin.Context) {
51
 	ut := service.AllService.UserService.Login(u, &model.LoginLog{
53
 	ut := service.AllService.UserService.Login(u, &model.LoginLog{
52
 		UserId:   u.Id,
54
 		UserId:   u.Id,
53
 		Client:   "webadmin",
55
 		Client:   "webadmin",
54
-		Uuid:     "",
56
+		Uuid:     "", //must be empty
55
 		Ip:       c.ClientIP(),
57
 		Ip:       c.ClientIP(),
56
 		Type:     "account",
58
 		Type:     "account",
57
 		Platform: f.Platform,
59
 		Platform: f.Platform,
@@ -82,3 +84,86 @@ func (ct *Login) Logout(c *gin.Context) {
82
 	}
84
 	}
83
 	response.Success(c, nil)
85
 	response.Success(c, nil)
84
 }
86
 }
87
+
88
+
89
+// LoginOptions
90
+// @Tags 登录
91
+// @Summary 登录选项
92
+// @Description 登录选项
93
+// @Accept  json
94
+// @Produce  json
95
+// @Success 200 {object} []string
96
+// @Failure 500 {object} response.ErrorResponse
97
+// @Router /admin/login-options [post]
98
+// 直接调用/api/login的LoginOptions方法
99
+func (ct *Login) LoginOptions(c *gin.Context) {
100
+	l := &api.Login{}
101
+    l.LoginOptions(c)
102
+}
103
+
104
+
105
+// OidcAuth
106
+// @Tags Oauth
107
+// @Summary OidcAuth
108
+// @Description OidcAuth
109
+// @Accept  json
110
+// @Produce  json
111
+// @Router /admin/oidc/auth [post]
112
+func (ct *Login) OidcAuth(c *gin.Context) {
113
+	// o := &api.Oauth{}
114
+	// o.OidcAuth(c)
115
+	f := &apiReq.OidcAuthRequest{}
116
+	err := c.ShouldBindJSON(f)
117
+	if err != nil {
118
+		response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
119
+		return
120
+	}
121
+
122
+	err, code, url := service.AllService.OauthService.BeginAuth(f.Op)
123
+	if err != nil {
124
+		response.Error(c, response.TranslateMsg(c, err.Error()))
125
+		return
126
+	}
127
+
128
+	service.AllService.OauthService.SetOauthCache(code, &service.OauthCacheItem{
129
+		Action: service.OauthActionTypeLogin,
130
+		Op:     	f.Op,
131
+		Id: 		f.Id,
132
+		DeviceType: "webadmin",
133
+		// DeviceOs: ct.Platform(c),
134
+		DeviceOs: 	f.DeviceInfo.Os,
135
+		Uuid: 		f.Uuid,
136
+	}, 5*60)
137
+
138
+	response.Success(c, gin.H{
139
+		"code": code,
140
+		"url":  url,
141
+	})
142
+}
143
+
144
+
145
+
146
+// OidcAuthQuery
147
+// @Tags Oauth
148
+// @Summary OidcAuthQuery
149
+// @Description OidcAuthQuery
150
+// @Accept  json
151
+// @Produce  json
152
+// @Success 200 {object} response.Response{data=adResp.LoginPayload}
153
+// @Failure 500 {object} response.Response
154
+// @Router /admin/oidc/auth-query [get]
155
+func (ct *Login) OidcAuthQuery(c *gin.Context) {
156
+	o := &api.Oauth{}
157
+	u, ut := o.OidcAuthQueryPre(c)
158
+	if ut == nil {
159
+		return
160
+	}
161
+	fmt.Println("u:", u)
162
+	fmt.Println("ut:", ut)
163
+	response.Success(c, &adResp.LoginPayload{
164
+		Token:      ut.Token,
165
+		Username:   u.Username,
166
+		RouteNames: service.AllService.UserService.RouteNames(u),
167
+		Nickname:   u.Nickname,
168
+	})
169
+}

+ 93 - 46
http/controller/api/ouath.go

@@ -59,36 +59,42 @@ func (o *Oauth) OidcAuth(c *gin.Context) {
59
 	})
59
 	})
60
 }
60
 }
61
 
61
 
62
-// OidcAuthQuery
63
-// @Tags Oauth
64
-// @Summary OidcAuthQuery
65
-// @Description OidcAuthQuery
66
-// @Accept  json
67
-// @Produce  json
68
-// @Success 200 {object} apiResp.LoginRes
69
-// @Failure 500 {object} response.ErrorResponse
70
-// @Router /oidc/auth-query [get]
71
-func (o *Oauth) OidcAuthQuery(c *gin.Context) {
62
+func (o *Oauth) OidcAuthQueryPre(c *gin.Context) (*model.User, *model.UserToken) {
63
+	var u *model.User
64
+	var ut *model.UserToken
72
 	q := &api.OidcAuthQuery{}
65
 	q := &api.OidcAuthQuery{}
73
-	err := c.ShouldBindQuery(q)
74
-	if err != nil {
75
-		response.Error(c, response.TranslateMsg(c, "ParamsError")+err.Error())
76
-		return
66
+
67
+	// 解析查询参数并处理错误
68
+	if err := c.ShouldBindQuery(q); err != nil {
69
+		response.Error(c, response.TranslateMsg(c, "ParamsError")+": "+err.Error())
70
+		return nil, nil
77
 	}
71
 	}
72
+
73
+	// 获取 OAuth 缓存
78
 	v := service.AllService.OauthService.GetOauthCache(q.Code)
74
 	v := service.AllService.OauthService.GetOauthCache(q.Code)
79
 	if v == nil {
75
 	if v == nil {
80
 		response.Error(c, response.TranslateMsg(c, "OauthExpired"))
76
 		response.Error(c, response.TranslateMsg(c, "OauthExpired"))
81
-		return
77
+		return nil, nil
82
 	}
78
 	}
79
+
80
+	// 如果 UserId 为 0,说明还在授权中
83
 	if v.UserId == 0 {
81
 	if v.UserId == 0 {
84
-		//正在授权
85
-		c.JSON(http.StatusOK, gin.H{})
86
-		return
82
+		c.JSON(http.StatusOK, gin.H{"message": "Authorization in progress"})
83
+		return nil, nil
87
 	}
84
 	}
88
-	u := service.AllService.UserService.InfoById(v.UserId)
89
-	//fmt.Println("auth success u", u)
85
+
86
+	// 获取用户信息
87
+	u = service.AllService.UserService.InfoById(v.UserId)
88
+	if u == nil {
89
+		response.Error(c, response.TranslateMsg(c, "UserNotFound"))
90
+		return nil, nil
91
+	}
92
+
93
+	// 删除 OAuth 缓存
90
 	service.AllService.OauthService.DeleteOauthCache(q.Code)
94
 	service.AllService.OauthService.DeleteOauthCache(q.Code)
91
-	ut := service.AllService.UserService.Login(u, &model.LoginLog{
95
+
96
+	// 创建登录日志并生成用户令牌
97
+	ut = service.AllService.UserService.Login(u, &model.LoginLog{
92
 		UserId:   u.Id,
98
 		UserId:   u.Id,
93
 		Client:   v.DeviceType,
99
 		Client:   v.DeviceType,
94
 		Uuid:     v.Uuid,
100
 		Uuid:     v.Uuid,
@@ -96,6 +102,27 @@ func (o *Oauth) OidcAuthQuery(c *gin.Context) {
96
 		Type:     model.LoginLogTypeOauth,
102
 		Type:     model.LoginLogTypeOauth,
97
 		Platform: v.DeviceOs,
103
 		Platform: v.DeviceOs,
98
 	})
104
 	})
105
+
106
+	if ut == nil {
107
+		response.Error(c, response.TranslateMsg(c, "LoginFailed"))
108
+		return nil, nil
109
+	}
110
+
111
+	// 返回用户令牌
112
+	return u, ut
113
+}
114
+
115
+// OidcAuthQuery
116
+// @Tags Oauth
117
+// @Summary OidcAuthQuery
118
+// @Description OidcAuthQuery
119
+// @Accept  json
120
+// @Produce  json
121
+// @Success 200 {object} apiResp.LoginRes
122
+// @Failure 500 {object} response.ErrorResponse
123
+// @Router /oidc/auth-query [get]
124
+func (o *Oauth) OidcAuthQuery(c *gin.Context) {
125
+	u, ut := o.OidcAuthQueryPre(c)
99
 	c.JSON(http.StatusOK, apiResp.LoginRes{
126
 	c.JSON(http.StatusOK, apiResp.LoginRes{
100
 		AccessToken: ut.Token,
127
 		AccessToken: ut.Token,
101
 		Type:        "access_token",
128
 		Type:        "access_token",
@@ -129,6 +156,7 @@ func (o *Oauth) OauthCallback(c *gin.Context) {
129
 
156
 
130
 	ty := v.Op
157
 	ty := v.Op
131
 	ac := v.Action
158
 	ac := v.Action
159
+	var u *model.User
132
 	//fmt.Println("ty ac ", ty, ac)
160
 	//fmt.Println("ty ac ", ty, ac)
133
 	if ty == model.OauthTypeGithub {
161
 	if ty == model.OauthTypeGithub {
134
 		code := c.Query("code")
162
 		code := c.Query("code")
@@ -145,7 +173,7 @@ func (o *Oauth) OauthCallback(c *gin.Context) {
145
 				return
173
 				return
146
 			}
174
 			}
147
 			//绑定
175
 			//绑定
148
-			u := service.AllService.UserService.InfoById(v.UserId)
176
+			u = service.AllService.UserService.InfoById(v.UserId)
149
 			if u == nil {
177
 			if u == nil {
150
 				c.String(http.StatusInternalServerError, response.TranslateMsg(c, "ItemNotFound"))
178
 				c.String(http.StatusInternalServerError, response.TranslateMsg(c, "ItemNotFound"))
151
 				return
179
 				return
@@ -164,7 +192,7 @@ func (o *Oauth) OauthCallback(c *gin.Context) {
164
 				c.String(http.StatusInternalServerError, response.TranslateMsg(c, "OauthHasBeenSuccess"))
192
 				c.String(http.StatusInternalServerError, response.TranslateMsg(c, "OauthHasBeenSuccess"))
165
 				return
193
 				return
166
 			}
194
 			}
167
-			u := service.AllService.UserService.InfoByGithubId(strconv.Itoa(userData.Id))
195
+			u = service.AllService.UserService.InfoByGithubId(strconv.Itoa(userData.Id))
168
 			if u == nil {
196
 			if u == nil {
169
 				oa := service.AllService.OauthService.InfoByOp(ty)
197
 				oa := service.AllService.OauthService.InfoByOp(ty)
170
 				if !*oa.AutoRegister {
198
 				if !*oa.AutoRegister {
@@ -184,15 +212,13 @@ func (o *Oauth) OauthCallback(c *gin.Context) {
184
 				}
212
 				}
185
 			}
213
 			}
186
 
214
 
187
-			v.UserId = u.Id
188
-			service.AllService.OauthService.SetOauthCache(cacheKey, v, 0)
189
-			c.String(http.StatusOK, response.TranslateMsg(c, "OauthSuccess"))
190
-			return
215
+			// v.UserId = u.Id
216
+			// service.AllService.OauthService.SetOauthCache(cacheKey, v, 0)
217
+			// c.String(http.StatusOK, response.TranslateMsg(c, "OauthSuccess"))
218
+			// return
191
 		}
219
 		}
192
 
220
 
193
-	}
194
-
195
-	if ty == model.OauthTypeGoogle {
221
+	} else if ty == model.OauthTypeGoogle {
196
 		code := c.Query("code")
222
 		code := c.Query("code")
197
 		err, userData := service.AllService.OauthService.GoogleCallback(code)
223
 		err, userData := service.AllService.OauthService.GoogleCallback(code)
198
 		if err != nil {
224
 		if err != nil {
@@ -209,7 +235,7 @@ func (o *Oauth) OauthCallback(c *gin.Context) {
209
 				return
235
 				return
210
 			}
236
 			}
211
 			//绑定
237
 			//绑定
212
-			u := service.AllService.UserService.InfoById(v.UserId)
238
+			u = service.AllService.UserService.InfoById(v.UserId)
213
 			if u == nil {
239
 			if u == nil {
214
 				c.String(http.StatusInternalServerError, response.TranslateMsg(c, "ItemNotFound"))
240
 				c.String(http.StatusInternalServerError, response.TranslateMsg(c, "ItemNotFound"))
215
 				return
241
 				return
@@ -227,7 +253,7 @@ func (o *Oauth) OauthCallback(c *gin.Context) {
227
 				c.String(http.StatusInternalServerError, response.TranslateMsg(c, "OauthHasBeenSuccess"))
253
 				c.String(http.StatusInternalServerError, response.TranslateMsg(c, "OauthHasBeenSuccess"))
228
 				return
254
 				return
229
 			}
255
 			}
230
-			u := service.AllService.UserService.InfoByGoogleEmail(userData.Email)
256
+			u = service.AllService.UserService.InfoByGoogleEmail(userData.Email)
231
 			if u == nil {
257
 			if u == nil {
232
 				oa := service.AllService.OauthService.InfoByOp(ty)
258
 				oa := service.AllService.OauthService.InfoByOp(ty)
233
 				if !*oa.AutoRegister {
259
 				if !*oa.AutoRegister {
@@ -248,13 +274,12 @@ func (o *Oauth) OauthCallback(c *gin.Context) {
248
 				}
274
 				}
249
 			}
275
 			}
250
 
276
 
251
-			v.UserId = u.Id
252
-			service.AllService.OauthService.SetOauthCache(cacheKey, v, 0)
253
-			c.String(http.StatusOK, response.TranslateMsg(c, "OauthSuccess"))
254
-			return
277
+			// v.UserId = u.Id
278
+			// service.AllService.OauthService.SetOauthCache(cacheKey, v, 0)
279
+			// c.String(http.StatusOK, response.TranslateMsg(c, "OauthSuccess"))
280
+			// return
255
 		}
281
 		}
256
-	}
257
-	if ty == model.OauthTypeOidc {
282
+	} else if ty == model.OauthTypeOidc {
258
 		code := c.Query("code")
283
 		code := c.Query("code")
259
 		err, userData := service.AllService.OauthService.OidcCallback(code)
284
 		err, userData := service.AllService.OauthService.OidcCallback(code)
260
 		if err != nil {
285
 		if err != nil {
@@ -271,7 +296,7 @@ func (o *Oauth) OauthCallback(c *gin.Context) {
271
 				return
296
 				return
272
 			}
297
 			}
273
 			//绑定
298
 			//绑定
274
-			u := service.AllService.UserService.InfoById(v.UserId)
299
+			u = service.AllService.UserService.InfoById(v.UserId)
275
 			if u == nil {
300
 			if u == nil {
276
 				c.String(http.StatusInternalServerError, response.TranslateMsg(c, "ItemNotFound"))
301
 				c.String(http.StatusInternalServerError, response.TranslateMsg(c, "ItemNotFound"))
277
 				return
302
 				return
@@ -289,7 +314,7 @@ func (o *Oauth) OauthCallback(c *gin.Context) {
289
 				c.String(http.StatusInternalServerError, response.TranslateMsg(c, "OauthHasBeenSuccess"))
314
 				c.String(http.StatusInternalServerError, response.TranslateMsg(c, "OauthHasBeenSuccess"))
290
 				return
315
 				return
291
 			}
316
 			}
292
-			u := service.AllService.UserService.InfoByOidcSub(userData.Sub)
317
+			u = service.AllService.UserService.InfoByOidcSub(userData.Sub)
293
 			if u == nil {
318
 			if u == nil {
294
 				oa := service.AllService.OauthService.InfoByOp(ty)
319
 				oa := service.AllService.OauthService.InfoByOp(ty)
295
 				if !*oa.AutoRegister {
320
 				if !*oa.AutoRegister {
@@ -311,13 +336,35 @@ func (o *Oauth) OauthCallback(c *gin.Context) {
311
 				}
336
 				}
312
 			}
337
 			}
313
 
338
 
314
-			v.UserId = u.Id
315
-			service.AllService.OauthService.SetOauthCache(cacheKey, v, 0)
316
-			c.String(http.StatusOK, response.TranslateMsg(c, "OauthSuccess"))
317
-			return
339
+			// v.UserId = u.Id
340
+			// service.AllService.OauthService.SetOauthCache(cacheKey, v, 0)
341
+			// c.String(http.StatusOK, response.TranslateMsg(c, "OauthSuccess"))
342
+			// return
318
 		}
343
 		}
319
 	}
344
 	}
320
-
321
-	c.String(http.StatusInternalServerError, response.TranslateMsg(c, "SystemError"))
345
+	// 如果u为空,说明没有绑定用户
346
+	if u == nil {
347
+		c.String(http.StatusInternalServerError, response.TranslateMsg(c, "SystemError"))
348
+		return
349
+	}
350
+	// 认证成功,设置缓存
351
+	v.UserId = u.Id
352
+	service.AllService.OauthService.SetOauthCache(cacheKey, v, 0)
353
+	// 如果是webadmin,登录成功后跳转到webadmin
354
+	if v.DeviceType == "webadmin" {
355
+		service.AllService.UserService.Login(u, &model.LoginLog{
356
+			UserId:   u.Id,
357
+			Client:   "webadmin",
358
+			Uuid:     "",//must be empty
359
+			Ip:       c.ClientIP(),
360
+			Type:     "account",
361
+			Platform: v.DeviceOs,
362
+		})
363
+		url := global.Config.Rustdesk.ApiServer + "/_admin/#/"
364
+		c.Redirect(http.StatusFound, url)
365
+		return
366
+	}
367
+	c.String(http.StatusOK, response.TranslateMsg(c, "OauthSuccess"))
368
+	return
322
 
369
 
323
 }
370
 }

+ 3 - 1
http/router/admin.go

@@ -33,7 +33,6 @@ func Init(g *gin.Engine) {
33
 	rs := &admin.Rustdesk{}
33
 	rs := &admin.Rustdesk{}
34
 	adg.GET("/server-config", rs.ServerConfig)
34
 	adg.GET("/server-config", rs.ServerConfig)
35
 	adg.GET("/app-config", rs.AppConfig)
35
 	adg.GET("/app-config", rs.AppConfig)
36
-
37
 	//访问静态文件
36
 	//访问静态文件
38
 	//g.StaticFS("/upload", http.Dir(global.Config.Gin.ResourcesPath+"/upload"))
37
 	//g.StaticFS("/upload", http.Dir(global.Config.Gin.ResourcesPath+"/upload"))
39
 }
38
 }
@@ -41,6 +40,9 @@ func LoginBind(rg *gin.RouterGroup) {
41
 	cont := &admin.Login{}
40
 	cont := &admin.Login{}
42
 	rg.POST("/login", cont.Login)
41
 	rg.POST("/login", cont.Login)
43
 	rg.POST("/logout", cont.Logout)
42
 	rg.POST("/logout", cont.Logout)
43
+	rg.GET("/login-options", cont.LoginOptions)
44
+	rg.POST("/oidc/auth", cont.OidcAuth)
45
+	rg.GET("/oidc/auth-query", cont.OidcAuthQuery)
44
 }
46
 }
45
 
47
 
46
 func UserBind(rg *gin.RouterGroup) {
48
 func UserBind(rg *gin.RouterGroup) {