@@ -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
@@ -1024,74 +1029,12 @@ func (api *API) oauthLogin(r *http.Request, params oauthLoginParams) (*http.Cook
1024
1029
}
1025
1030
}
1026
1031
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 )
1032
+ // If you do a convert to OIDC and your email does not match, we need to
1033
+ // catch this and not make a new account.
1034
+ if isMergeStateString (params .State .StateString ) {
1035
+ err := api .convertUserToOauth (ctx , r , tx , params )
1065
1036
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
- }
1037
+ return err
1095
1038
}
1096
1039
}
1097
1040
@@ -1258,6 +1201,91 @@ func (api *API) oauthLogin(r *http.Request, params oauthLoginParams) (*http.Cook
1258
1201
return cookie , * key , nil
1259
1202
}
1260
1203
1204
+ // convertUserToOauth will convert a user from password base loginType to
1205
+ // an oauth login type. If it fails, it will return a httpError
1206
+ func (api * API ) convertUserToOauth (ctx context.Context , r * http.Request , db database.Store , params oauthLoginParams ) error {
1207
+ user := params .User
1208
+
1209
+ // Trying to convert to OIDC, but the email does not match.
1210
+ // So do not make a new user, just block the request.
1211
+ if user .ID == uuid .Nil {
1212
+ return httpError {
1213
+ code : http .StatusBadRequest ,
1214
+ 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 ),
1215
+ }
1216
+ }
1217
+
1218
+ // nolint:gocritic // Required to auth the oidc convert
1219
+ mergeState , err := db .GetUserOauthMergeState (dbauthz .AsSystemRestricted (ctx ), database.GetUserOauthMergeStateParams {
1220
+ UserID : user .ID ,
1221
+ StateString : params .State .StateString ,
1222
+ })
1223
+ if xerrors .Is (err , sql .ErrNoRows ) {
1224
+ return httpError {
1225
+ code : http .StatusBadRequest ,
1226
+ msg : "No convert login request found with given state. Restart the convert process and try again." ,
1227
+ }
1228
+ }
1229
+ if err != nil {
1230
+ return httpError {
1231
+ code : http .StatusInternalServerError ,
1232
+ msg : err .Error (),
1233
+ }
1234
+ }
1235
+
1236
+ // At this point, this request could be an attempt to convert from
1237
+ // password auth to oauth auth. Always log these attempts.
1238
+ var (
1239
+ auditor = * api .Auditor .Load ()
1240
+ oauthConvertAudit , commitOauthConvertAudit = params .InitAuditRequest (& audit.RequestParams {
1241
+ Audit : auditor ,
1242
+ Log : api .Logger ,
1243
+ Request : r ,
1244
+ Action : database .AuditActionLogin ,
1245
+ })
1246
+ )
1247
+ oauthConvertAudit .Old = mergeState
1248
+ defer commitOauthConvertAudit ()
1249
+
1250
+ // If we do not allow converting to oauth, return an error.
1251
+ if ! params .OauthConversionEnabled {
1252
+ return httpError {
1253
+ code : http .StatusForbidden ,
1254
+ msg : fmt .Sprintf ("Incorrect login type, attempting to use %q but user is of login type %q" ,
1255
+ params .LoginType ,
1256
+ user .LoginType ,
1257
+ ),
1258
+ }
1259
+ }
1260
+
1261
+ // Make sure the merge state generated matches this OIDC login request.
1262
+ // It needs to have the correct login type information for this
1263
+ // user.
1264
+ if user .ID != mergeState .UserID || user .LoginType != mergeState .FromLoginType || params .LoginType != mergeState .ToLoginType {
1265
+ return httpError {
1266
+ code : http .StatusForbidden ,
1267
+ msg : fmt .Sprintf ("Request to convert login type from %s to %s failed" , user .LoginType , params .LoginType ),
1268
+ }
1269
+ }
1270
+
1271
+ // Convert the user and default to the normal login flow.
1272
+ // If the login succeeds, this transaction will commit and the user
1273
+ // will be converted.
1274
+ // nolint:gocritic // system query to update user login type
1275
+ user , err = db .UpdateUserLoginType (dbauthz .AsSystemRestricted (ctx ), database.UpdateUserLoginTypeParams {
1276
+ LoginType : params .LoginType ,
1277
+ UserID : user .ID ,
1278
+ })
1279
+ if err != nil {
1280
+ return httpError {
1281
+ code : http .StatusInternalServerError ,
1282
+ msg : "Failed to convert user to new login type" ,
1283
+ }
1284
+ }
1285
+ return nil
1286
+
1287
+ }
1288
+
1261
1289
// githubLinkedID returns the unique ID for a GitHub user.
1262
1290
func githubLinkedID (u * github.User ) string {
1263
1291
return strconv .FormatInt (u .GetID (), 10 )
@@ -1324,3 +1352,10 @@ func findLinkedUser(ctx context.Context, db database.Store, linkedID string, ema
1324
1352
1325
1353
return user , link , nil
1326
1354
}
1355
+
1356
+ func isMergeStateString (state string ) bool {
1357
+ if strings .HasPrefix (state , mergeStateStringPrefix ) {
1358
+ return true
1359
+ }
1360
+ return false
1361
+ }
0 commit comments