SpreadSheets commited on
Commit
b1df455
·
1 Parent(s): 1e207a2

feat: enhance OAuth login flow and improve error handling

Browse files
Files changed (1) hide show
  1. app/__init__.py +55 -59
app/__init__.py CHANGED
@@ -1,11 +1,10 @@
1
  from typing import Optional
2
 
 
3
  from flask_dance.consumer import oauth_authorized, oauth_error
4
  from flask_dance.contrib.discord import make_discord_blueprint
5
  from flask_dance.contrib.google import make_google_blueprint
6
- from flask import Flask, flash, redirect, url_for
7
- from flask_login import login_user
8
- import requests
9
 
10
  from .extensions import db, login_manager, migrate
11
  from .models import OAuth, User
@@ -36,14 +35,16 @@ def create_app():
36
  "https://www.googleapis.com/auth/userinfo.email",
37
  "openid",
38
  ],
 
 
39
  )
40
  app.register_blueprint(google_bp, url_prefix="/login")
41
 
42
  discord_bp = make_discord_blueprint(
43
- client_secret=app.config["DISCORD_CLIENT_SECRET"],
44
- scope=["identify", "email", "guilds.join"],
45
  client_id=app.config["DISCORD_CLIENT_ID"],
46
- prompt="consent",
 
 
47
  )
48
  app.register_blueprint(discord_bp, url_prefix="/login")
49
 
@@ -56,21 +57,41 @@ def create_app():
56
  ):
57
  if not email:
58
  flash(
59
- f"{provider_name.title()} Account Must Include A Public Email.", "error"
60
  )
61
  return False
62
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
63
  oauth = OAuth.query.filter_by(
64
- provider_user_id=provider_user_id,
65
  provider=provider_name,
66
- ).one_or_none()
 
67
 
68
  if oauth:
69
  user = oauth.user
70
  oauth.token = token
71
  else:
72
- user = User.query.filter_by(email=email).one_or_none()
73
-
74
  if not user:
75
  user = User(name=name or email.split("@")[0], email=email)
76
  db.session.add(user)
@@ -82,61 +103,40 @@ def create_app():
82
  token=token,
83
  user=user,
84
  )
85
-
86
  db.session.add(oauth)
87
 
88
  db.session.commit()
89
 
90
  login_user(user)
91
-
92
  flash(f"Signed In With {provider_name.title()}", "success")
93
  return redirect(url_for("main.index"))
94
 
95
- def _join_discord_guild(discord_user_id: str, access_token: str):
96
- bot_token = app.config.get("DISCORD_BOT_TOKEN")
97
- guild_id = app.config.get("DISCORD_GUILD_ID")
98
-
99
- if not guild_id or not bot_token:
100
- return
101
-
102
- url = f"https://discord.com/api/v10/guilds/{guild_id}/members/{discord_user_id}"
103
- headers = {"Authorization": f"Bot {bot_token}"}
104
- payload = {"access_token": access_token}
105
-
106
- try:
107
- response = requests.put(url, headers=headers, json=payload, timeout=10)
108
-
109
- except requests.RequestException as exc:
110
- app.logger.warning("Failed To Add User To Discord Guild: %s", exc)
111
- return
112
-
113
- if response.status_code not in (200, 201, 204):
114
- app.logger.warning(
115
- "Discord Guild Join Failed (%s): %s",
116
- response.status_code,
117
- response.text,
118
- )
119
-
120
  @oauth_authorized.connect_via(google_bp)
121
  def google_logged_in(blueprint, token):
122
  if not token:
123
- flash("Google Authentication Failed.", "error")
124
  return False
125
 
126
  resp = blueprint.session.get("/oauth2/v2/userinfo")
127
-
128
  if not resp.ok:
129
- flash("Unable To Read Google Profile Information.", "error")
130
  return False
131
 
132
- google_info = resp.json()
133
- google_user_id = str(google_info["id"])
 
 
 
 
 
 
 
134
 
135
  return _finish_login(
136
- provider_user_id=google_user_id,
137
- email=google_info.get("email"),
138
  provider_name=blueprint.name,
139
- name=google_info.get("name"),
 
 
140
  token=token,
141
  )
142
 
@@ -148,31 +148,27 @@ def create_app():
148
 
149
  resp = blueprint.session.get("/api/users/@me")
150
  if not resp.ok:
151
- flash("Unable To Read Discord Profile Information.", "error")
152
  return False
153
 
154
- discord_info = resp.json()
155
- discord_user_id = str(discord_info["id"])
156
 
157
- redirect_response = _finish_login(
158
- name=discord_info.get("username"),
159
- provider_user_id=discord_user_id,
160
- email=discord_info.get("email"),
161
  provider_name=blueprint.name,
 
 
 
162
  token=token,
163
  )
164
 
165
- if redirect_response and token.get("access_token"):
166
- _join_discord_guild(discord_user_id, token["access_token"])
167
-
168
- return redirect_response
169
 
170
  @oauth_error.connect_via(google_bp)
171
  @oauth_error.connect_via(discord_bp)
172
  def oauth_error_handler(blueprint, error, error_description=None, error_uri=None):
173
- message = f"{blueprint.name.title()} OAuth error: {error_description or error}."
174
  app.logger.error(message)
175
-
176
  flash(message, "error")
177
 
178
  return app
 
1
  from typing import Optional
2
 
3
+ from flask import Flask, flash, redirect, url_for
4
  from flask_dance.consumer import oauth_authorized, oauth_error
5
  from flask_dance.contrib.discord import make_discord_blueprint
6
  from flask_dance.contrib.google import make_google_blueprint
7
+ from flask_login import current_user, login_user
 
 
8
 
9
  from .extensions import db, login_manager, migrate
10
  from .models import OAuth, User
 
35
  "https://www.googleapis.com/auth/userinfo.email",
36
  "openid",
37
  ],
38
+ redirect_url="/login/google/authorized",
39
+ reprompt_consent=True,
40
  )
41
  app.register_blueprint(google_bp, url_prefix="/login")
42
 
43
  discord_bp = make_discord_blueprint(
 
 
44
  client_id=app.config["DISCORD_CLIENT_ID"],
45
+ client_secret=app.config["DISCORD_CLIENT_SECRET"],
46
+ scope=["identify", "email"],
47
+ redirect_url="/login/discord/authorized",
48
  )
49
  app.register_blueprint(discord_bp, url_prefix="/login")
50
 
 
57
  ):
58
  if not email:
59
  flash(
60
+ f"{provider_name.title()} Account Must Contain A Public Email.", "error"
61
  )
62
  return False
63
 
64
+ if current_user.is_authenticated:
65
+ user = current_user
66
+ oauth = OAuth.query.filter_by(
67
+ provider=provider_name, provider_user_id=provider_user_id
68
+ ).first()
69
+
70
+ if not oauth:
71
+ oauth = OAuth(
72
+ provider=provider_name,
73
+ provider_user_id=provider_user_id,
74
+ token=token,
75
+ user_id=user.id,
76
+ )
77
+ db.session.add(oauth)
78
+ else:
79
+ oauth.token = token
80
+
81
+ db.session.commit()
82
+ flash(f"{provider_name.title()} Account Linked!", "success")
83
+ return redirect(url_for("main.settings"))
84
+
85
  oauth = OAuth.query.filter_by(
 
86
  provider=provider_name,
87
+ provider_user_id=provider_user_id,
88
+ ).first()
89
 
90
  if oauth:
91
  user = oauth.user
92
  oauth.token = token
93
  else:
94
+ user = User.query.filter_by(email=email).first()
 
95
  if not user:
96
  user = User(name=name or email.split("@")[0], email=email)
97
  db.session.add(user)
 
103
  token=token,
104
  user=user,
105
  )
 
106
  db.session.add(oauth)
107
 
108
  db.session.commit()
109
 
110
  login_user(user)
 
111
  flash(f"Signed In With {provider_name.title()}", "success")
112
  return redirect(url_for("main.index"))
113
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
114
  @oauth_authorized.connect_via(google_bp)
115
  def google_logged_in(blueprint, token):
116
  if not token:
117
+ flash("Google Authentication Ffailed.", "error")
118
  return False
119
 
120
  resp = blueprint.session.get("/oauth2/v2/userinfo")
 
121
  if not resp.ok:
122
+ flash("Failed To load Google Profile.", "error")
123
  return False
124
 
125
+ info = resp.json()
126
+ google_user_id = str(info["id"])
127
+
128
+ if "refresh_token" in token:
129
+ existing = OAuth.query.filter_by(
130
+ provider=blueprint.name, provider_user_id=google_user_id
131
+ ).first()
132
+ if existing:
133
+ existing.token["refresh_token"] = token["refresh_token"]
134
 
135
  return _finish_login(
 
 
136
  provider_name=blueprint.name,
137
+ provider_user_id=google_user_id,
138
+ email=info.get("email"),
139
+ name=info.get("name"),
140
  token=token,
141
  )
142
 
 
148
 
149
  resp = blueprint.session.get("/api/users/@me")
150
  if not resp.ok:
151
+ flash("Failed To Load Discord Profile.", "error")
152
  return False
153
 
154
+ info = resp.json()
155
+ discord_user_id = str(info["id"])
156
 
157
+ response = _finish_login(
 
 
 
158
  provider_name=blueprint.name,
159
+ provider_user_id=discord_user_id,
160
+ email=info.get("email"),
161
+ name=info.get("username"),
162
  token=token,
163
  )
164
 
165
+ return response
 
 
 
166
 
167
  @oauth_error.connect_via(google_bp)
168
  @oauth_error.connect_via(discord_bp)
169
  def oauth_error_handler(blueprint, error, error_description=None, error_uri=None):
170
+ message = f"{blueprint.name.title()} OAuth error: {error_description or error}"
171
  app.logger.error(message)
 
172
  flash(message, "error")
173
 
174
  return app