@@ -32,6 +32,8 @@ import (
32
32
"github.com/coder/coder/cryptorand"
33
33
)
34
34
35
+ const mergeStateStringPrefix = "convert-"
36
+
35
37
// postConvertLoginType replies with an oauth state token capable of converting
36
38
// the user to an oauth user.
37
39
//
@@ -105,6 +107,9 @@ func (api *API) postConvertLoginType(rw http.ResponseWriter, r *http.Request) {
105
107
})
106
108
return
107
109
}
110
+ // The prefix is used to identify this state string as a conversion state
111
+ // without needing to hit the database. The random string is the CSRF protection.
112
+ stateString = fmt .Sprintf ("%s%s" , mergeStateStringPrefix , stateString )
108
113
109
114
now := time .Now ()
110
115
var mergeState database.OauthMergeState
@@ -406,6 +411,7 @@ func (api *API) userAuthMethods(rw http.ResponseWriter, r *http.Request) {
406
411
407
412
httpapi .Write (r .Context (), rw , http .StatusOK , codersdk.AuthMethods {
408
413
UserAuthenticationType : currentUserLoginType ,
414
+ ConvertToOIDCEnabled : api .Options .DeploymentValues .EnableOauthAccountConversion .Value (),
409
415
Password : codersdk.AuthMethod {
410
416
Enabled : ! api .DeploymentValues .DisablePasswordAuth .Value (),
411
417
},
@@ -1024,74 +1030,12 @@ func (api *API) oauthLogin(r *http.Request, params oauthLoginParams) (*http.Cook
1024
1030
}
1025
1031
}
1026
1032
1027
- // If this is not a new user and the login type is different,
1028
- // we need to check if the user is trying to change their login type.
1029
- if user .ID != uuid .Nil && user .LoginType != params .LoginType {
1030
- wrongLoginTypeErr := httpError {
1031
- code : http .StatusForbidden ,
1032
- msg : fmt .Sprintf ("Incorrect login type, attempting to use %q but user is of login type %q" ,
1033
- params .LoginType ,
1034
- user .LoginType ,
1035
- ),
1036
- }
1037
- // If we do not allow converting to oauth, return an error.
1038
- if ! params .OauthConversionEnabled {
1039
- return wrongLoginTypeErr
1040
- }
1041
-
1042
- // At this point, this request could be an attempt to convert from
1043
- // password auth to oauth auth.
1044
- var (
1045
- auditor = * api .Auditor .Load ()
1046
- oauthConvertAudit , commitOauthConvertAudit = params .InitAuditRequest (& audit.RequestParams {
1047
- Audit : auditor ,
1048
- Log : api .Logger ,
1049
- Request : r ,
1050
- Action : database .AuditActionLogin ,
1051
- })
1052
- )
1053
- defer commitOauthConvertAudit ()
1054
-
1055
- // nolint:gocritic // Required to auth the oidc convert
1056
- mergeState , err := tx .GetUserOauthMergeState (dbauthz .AsSystemRestricted (ctx ), database.GetUserOauthMergeStateParams {
1057
- UserID : user .ID ,
1058
- StateString : params .State .StateString ,
1059
- })
1060
- if xerrors .Is (err , sql .ErrNoRows ) {
1061
- return wrongLoginTypeErr
1062
- }
1063
-
1064
- failedMsg := fmt .Sprintf ("Request to convert login type from %s to %s failed" , user .LoginType , params .LoginType )
1033
+ // If you do a convert to OIDC and your email does not match, we need to
1034
+ // catch this and not make a new account.
1035
+ if isMergeStateString (params .State .StateString ) {
1036
+ err := api .convertUserToOauth (ctx , r , tx , params )
1065
1037
if err != nil {
1066
- return httpError {
1067
- code : http .StatusForbidden ,
1068
- msg : failedMsg ,
1069
- }
1070
- }
1071
- oauthConvertAudit .Old = mergeState
1072
- // Make sure the merge state generated matches this OIDC login request.
1073
- // It needs to have the correct login type information for this
1074
- // user.
1075
- if user .ID != mergeState .UserID || user .LoginType != mergeState .FromLoginType || params .LoginType != mergeState .ToLoginType {
1076
- return httpError {
1077
- code : http .StatusForbidden ,
1078
- msg : failedMsg ,
1079
- }
1080
- }
1081
-
1082
- // Convert the user and default to the normal login flow.
1083
- // If the login succeeds, this transaction will commit and the user
1084
- // will be converted.
1085
- // nolint:gocritic // system query to update user login type
1086
- user , err = tx .UpdateUserLoginType (dbauthz .AsSystemRestricted (ctx ), database.UpdateUserLoginTypeParams {
1087
- LoginType : params .LoginType ,
1088
- UserID : user .ID ,
1089
- })
1090
- if err != nil {
1091
- return httpError {
1092
- code : http .StatusInternalServerError ,
1093
- msg : "Failed to convert user to new login type" ,
1094
- }
1038
+ return err
1095
1039
}
1096
1040
}
1097
1041
@@ -1258,6 +1202,91 @@ func (api *API) oauthLogin(r *http.Request, params oauthLoginParams) (*http.Cook
1258
1202
return cookie , * key , nil
1259
1203
}
1260
1204
1205
+ // convertUserToOauth will convert a user from password base loginType to
1206
+ // an oauth login type. If it fails, it will return a httpError
1207
+ func (api * API ) convertUserToOauth (ctx context.Context , r * http.Request , db database.Store , params oauthLoginParams ) error {
1208
+ user := params .User
1209
+
1210
+ // Trying to convert to OIDC, but the email does not match.
1211
+ // So do not make a new user, just block the request.
1212
+ if user .ID == uuid .Nil {
1213
+ return httpError {
1214
+ code : http .StatusBadRequest ,
1215
+ msg : fmt .Sprintf ("The oidc account with the email %q does not match the email of the account you are trying to convert. Contact your administrator to resolve this issue." , params .Email ),
1216
+ }
1217
+ }
1218
+
1219
+ // nolint:gocritic // Required to auth the oidc convert
1220
+ mergeState , err := db .GetUserOauthMergeState (dbauthz .AsSystemRestricted (ctx ), database.GetUserOauthMergeStateParams {
1221
+ UserID : user .ID ,
1222
+ StateString : params .State .StateString ,
1223
+ })
1224
+ if xerrors .Is (err , sql .ErrNoRows ) {
1225
+ return httpError {
1226
+ code : http .StatusBadRequest ,
1227
+ msg : "No convert login request found with given state. Restart the convert process and try again." ,
1228
+ }
1229
+ }
1230
+ if err != nil {
1231
+ return httpError {
1232
+ code : http .StatusInternalServerError ,
1233
+ msg : err .Error (),
1234
+ }
1235
+ }
1236
+
1237
+ // At this point, this request could be an attempt to convert from
1238
+ // password auth to oauth auth. Always log these attempts.
1239
+ var (
1240
+ auditor = * api .Auditor .Load ()
1241
+ oauthConvertAudit , commitOauthConvertAudit = params .InitAuditRequest (& audit.RequestParams {
1242
+ Audit : auditor ,
1243
+ Log : api .Logger ,
1244
+ Request : r ,
1245
+ Action : database .AuditActionLogin ,
1246
+ })
1247
+ )
1248
+ oauthConvertAudit .Old = mergeState
1249
+ defer commitOauthConvertAudit ()
1250
+
1251
+ // If we do not allow converting to oauth, return an error.
1252
+ if ! params .OauthConversionEnabled {
1253
+ return httpError {
1254
+ code : http .StatusForbidden ,
1255
+ msg : fmt .Sprintf ("Incorrect login type, attempting to use %q but user is of login type %q" ,
1256
+ params .LoginType ,
1257
+ user .LoginType ,
1258
+ ),
1259
+ }
1260
+ }
1261
+
1262
+ // Make sure the merge state generated matches this OIDC login request.
1263
+ // It needs to have the correct login type information for this
1264
+ // user.
1265
+ if user .ID != mergeState .UserID || user .LoginType != mergeState .FromLoginType || params .LoginType != mergeState .ToLoginType {
1266
+ return httpError {
1267
+ code : http .StatusForbidden ,
1268
+ msg : fmt .Sprintf ("Request to convert login type from %s to %s failed" , user .LoginType , params .LoginType ),
1269
+ }
1270
+ }
1271
+
1272
+ // Convert the user and default to the normal login flow.
1273
+ // If the login succeeds, this transaction will commit and the user
1274
+ // will be converted.
1275
+ // nolint:gocritic // system query to update user login type
1276
+ user , err = db .UpdateUserLoginType (dbauthz .AsSystemRestricted (ctx ), database.UpdateUserLoginTypeParams {
1277
+ LoginType : params .LoginType ,
1278
+ UserID : user .ID ,
1279
+ })
1280
+ if err != nil {
1281
+ return httpError {
1282
+ code : http .StatusInternalServerError ,
1283
+ msg : "Failed to convert user to new login type" ,
1284
+ }
1285
+ }
1286
+ return nil
1287
+
1288
+ }
1289
+
1261
1290
// githubLinkedID returns the unique ID for a GitHub user.
1262
1291
func githubLinkedID (u * github.User ) string {
1263
1292
return strconv .FormatInt (u .GetID (), 10 )
@@ -1324,3 +1353,10 @@ func findLinkedUser(ctx context.Context, db database.Store, linkedID string, ema
1324
1353
1325
1354
return user , link , nil
1326
1355
}
1356
+
1357
+ func isMergeStateString (state string ) bool {
1358
+ if strings .HasPrefix (state , mergeStateStringPrefix ) {
1359
+ return true
1360
+ }
1361
+ return false
1362
+ }
0 commit comments