-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathmain.py
191 lines (153 loc) · 6.61 KB
/
main.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
import os
import redis
import json
from flask import Flask, g, session, request, render_template, redirect, url_for
from flask_cas import CAS, login_required, logout
from dotenv import load_dotenv
from werkzeug.exceptions import HTTPException
from discord import OAUTH_URL, VERIFIED_ROLE_ID, SERVER_ID, get_tokens, get_user_info, get_member, add_user_to_server, add_role_to_member, kick_member_from_server, set_member_nickname
import requests
print("CONNECTING TO REDIS")
# Connect to Redis
db = redis.from_url(os.environ.get('REDIS_URL'),
charset='utf-8', decode_responses=True)
print("CONNECTED TO REDIS")
# Load .env into os.environ
load_dotenv()
app = Flask(__name__)
cas = CAS(app, '/cas')
app.secret_key = os.environ.get('FLASK_SECRET_KEY')
app.config['SITE_TITLE'] = os.environ.get('SITE_TITLE')
app.config['CAS_SERVER'] = 'https://cas.auth.rpi.edu/cas/login'# 'https://cas-auth.rpi.edu/cas/login'
app.config['CAS_AFTER_LOGIN'] = 'index'
@app.route('/test')
def test():
return 'Testing'
@app.route('/test_redis')
def test_redis():
val = db.get('test')
return 'Testing redis: ' + val
@app.route('/', methods=['GET', 'POST'])
@login_required
def index():
app.logger.info('HOME PAGE REQUEST')
if request.method == 'GET':
# If member is already verified, redirect to joined page
print("BEFORE REDIS")
if db.sismember('verified', cas.username):
print("IN IF")
return redirect(url_for('joined'))
print("AFTER REDIS")
# User profile might be in DB already if reconnected and disconnected
user = {}
if db.get('users:' + cas.username) != None:
user = json.loads(db.get('users:' + cas.username))
app.logger.info(f'Home page requested by {cas.username}')
return render_template('join.html', user=user, rcs_id=cas.username.lower())
elif request.method == 'POST':
# Limit to 20 characters so overall Discord nickname doesn't exceed limit of 32 characters
first_name = request.form['first_name'].strip()[:20]
last_name = request.form['last_name'].strip()
graduation_year = request.form['graduation_year'].strip()
user = {
'first_name': first_name,
'last_name': last_name,
'graduation_year': graduation_year
}
db.set('users:' + cas.username, json.dumps(user))
app.logger.info(f'Redirecting {cas.username} to Discord OAuth page')
return redirect(OAUTH_URL)
@app.route('/discord/callback', methods=['GET'])
@login_required
def discord_callback():
app.logger.info('DISCORD CALLBACK REQUEST')
# Extract code or error from URL
authorization_code = request.args.get('code')
error = request.args.get('error')
if error:
# Handle the special case where the user declined to connect
if error == 'access_denied':
app.logger.error(
f'{cas.username} declined to connect their Discord account')
return render_template('error.html', error='You declined to connect your Discord account!')
else:
# Handle generic Discord error
error_description = request.args.get('error_description')
app.logger.error(
f'An error occurred on the Discord callback for {cas.username}: {error_description}')
raise Exception(error_description)
# Get user from DB
user = json.loads(db.get('users:' + cas.username))
# Generate nickname as "<first name> <last name initial> '<2 digit graduation year> (<rcs id>)"
# e.g. "Frank M '22 (matraf)"
nickname = user['first_name'] + ' ' + \
user['last_name'][0] + " '" + user['graduation_year'][2:] + \
f' ({cas.username.lower()})'
# Exchange authorization code for tokens
tokens = get_tokens(authorization_code)
# Get info on the Discord user that just connected (really only need id)
discord_user = get_user_info(tokens['access_token'])
# Save to DB
db.set('discord_user_ids:' + cas.username, discord_user['id'])
db.set('access_tokens:' + cas.username, tokens['access_token'])
if not db.sismember('verified', cas.username):
# Add them to the server
add_user_to_server(tokens['access_token'],
discord_user['id'], nickname)
app.logger.info(f'Added {cas.username} to Discord server')
# Set their nickname
try:
set_member_nickname(discord_user['id'], nickname)
app.logger.info(
f'Set {cas.username}\'s nickname to "{nickname}" on server')
except requests.exceptions.HTTPError as e:
app.logger.warning(
f'Failed to set nickname "{nickname}" to {cas.username} on server: {e}')
# Give them the verified role
try:
add_role_to_member(discord_user['id'], VERIFIED_ROLE_ID)
app.logger.info(f'Added verified role to {cas.username} on server')
except requests.exceptions.HTTPError as e:
app.logger.warning(
f'Failed to add role to {cas.username} on server: {e}')
# Mark success
db.sadd('verified', cas.username)
return redirect(url_for('joined'))
@app.route('/discord/reset')
@login_required
def reset_discord():
discord_user_id = db.get('discord_user_ids:' + cas.username)
# Attempt to kick member from server and then remove DB records
try:
kick_member_from_server(discord_user_id)
db.delete('discord_user_ids:' + cas.username)
db.delete('access_tokens:' + cas.username)
db.srem('verified', cas.username)
except:
raise Exception('Failed to kick your old account from the server.')
return redirect('/')
@app.route('/joined')
@login_required
def joined():
app.logger.info('JOINED REQUEST')
if not db.sismember('verified', cas.username):
app.logger.info('REDIRECT TO / REQUEST')
return redirect('/')
user = json.loads(db.get('users:' + cas.username))
return render_template('joined.html', rcs_id=cas.username.lower(), user=user, discord_server_id=SERVER_ID)
@app.errorhandler(404)
def page_not_found(e):
'''Render 404 page.'''
return render_template('404.html'), 404
@app.errorhandler(Exception)
def handle_exception(e):
'''Handles all unhandled exceptions.'''
# Handle HTTP errors
if isinstance(e, HTTPException):
return render_template('error.html', error=e), e.code
# Handle non-HTTP errors
app.logger.exception(e)
# Hide error details in production
if app.env == 'production':
e = 'Something went wrong... Please try again later.'
return render_template('error.html', error=e), 500