11import ipaddress
2+ import re
23import secrets
34import urllib .parse
45
2627@discord_blueprint .route ("/auth" , methods = ["GET" ])
2728def discord_auth ():
2829 ip = request .args .get ("ip" )
30+ ip_str = None
2931 if not isinstance (ip , str ):
3032 return jsonify ({"error" : "provided IP address invalid" }), 400
3133 try :
34+ ip_str = ip
3235 ip = ipaddress .ip_address (ip )
3336 except ValueError :
3437 return jsonify ({"error" : "provided IP address invalid" }), 400
38+ if "," in ip_str :
39+ return jsonify ({"error" : "provided IP address invalid" }), 400
3540 if ip .version == 6 :
3641 return jsonify ({"error" : "IPv6 address not allowed" }), 400
3742 if ip .is_multicast or ip .is_unspecified :
3843 return jsonify ({"error" : "multicast or unspecified address not allowed" }), 400
3944 seeker_port = request .args .get ("seeker_port" )
40- if not isinstance (seeker_port , str ) or not seeker_port . isdigit ( ):
45+ if not isinstance (seeker_port , str ) or not re . match ( "^[0-9]+$" , seeker_port ):
4146 seeker_port = ""
4247 try :
4348 seeker_port = int (seeker_port )
4449 except ValueError :
4550 seeker_port = ""
4651 if not isinstance (seeker_port , int ) or seeker_port > 65535 or seeker_port < 1023 :
4752 seeker_port = ""
53+ nonce = request .args .get ("nonce" )
54+ if not isinstance (nonce , str ) or len (nonce ) != 64 or not re .match ("^[a-z0-9]+$" , nonce ):
55+ return jsonify ({"error" : "bad nonce" }), 400
4856 session ["oauth2_state" ] = (
49- f"{ urllib .parse .quote (ip . exploded , safe = "" , encoding = "utf-8" )} ,{ seeker_port } ,{ secrets .token_urlsafe (16 )} "
57+ f"{ urllib .parse .quote (ip_str , safe = "" , encoding = "utf-8" )} ,{ seeker_port } , { nonce } ,{ secrets .token_urlsafe (16 )} "
5058 )
5159 return redirect (discord_client .generate_uri (scope = ["identify" ], state = session ["oauth2_state" ]))
5260
@@ -65,8 +73,34 @@ def discord_callback():
6573 del session ["oauth2_state" ]
6674
6775 state_attrs = state .split ("," )
76+ if len (state_attrs ) != 4 :
77+ return jsonify ({"error" : "bad state" }), 400
6878 ip = urllib .parse .unquote (state_attrs [0 ])
69- seeker_port = state_attrs [1 ]
79+ try :
80+ seeker_port = int (state_attrs [1 ])
81+ except ValueError :
82+ return jsonify ({"error" : "bad state" }), 400
83+ nonce = state_attrs [2 ]
84+ if not isinstance (nonce , str ) or len (nonce ) != 64 or not re .match ("^[a-z0-9]+$" , nonce ):
85+ return jsonify ({"error" : "bad state" }), 400
86+ nonce_duration = cfg .API .get ("nonce-valid-duration" )
87+ if nonce_duration is None :
88+ nonce_duration = 240
89+ try :
90+ nonce_valid , reason_invalid = db .SessionCreationNonce .is_valid_session_creation (
91+ ip , seeker_port , nonce , nonce_duration
92+ )
93+ if not nonce_valid :
94+ notice = ""
95+ if reason_invalid == "invalid" :
96+ notice = " account security risk: check if connected to a genuine BeeStation game server."
97+ elif reason_invalid == "expired" :
98+ notice = " log in within a shorter time period."
99+ return jsonify ({"error" : f"{ reason_invalid or "invalid" } nonce.{ notice } " }), 401
100+ except Exception as e :
101+ current_app .logger .error (f"error while checking nonce: { e } " )
102+ return jsonify ({"error" : "error checking nonce" }), 500
103+
70104 discord_uid = None
71105 discord_username = None
72106
0 commit comments