Skip to content

Commit 7c9eecc

Browse files
committed
Add social linking for Github
1 parent 158c266 commit 7c9eecc

6 files changed

Lines changed: 164 additions & 34 deletions

File tree

app/account.py

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,66 @@ def update_password():
109109

110110
return render_template("account/update-password.html", form=form, profile=profile)
111111

112+
@account.route("/connect", methods=["GET", "POST"])
113+
@login_required
114+
@profile_required
115+
def list_identities():
116+
profile = get_profile_by_user()
117+
res = supabase.auth.get_user_identities()
118+
identities = res.identities
119+
connected_identities = list(
120+
map(lambda identity: identity.provider, res.identities)
121+
)
122+
form = UpdatePasswordForm()
123+
if form.validate_on_submit():
124+
password = form.password.data
125+
126+
try:
127+
user = supabase.auth.update_user(attributes={"password": password})
128+
129+
if user:
130+
flash("Your password was updated successfully.", "info")
131+
session.pop("password_update_required", None)
132+
else:
133+
flash("Updating your password failed, please try again.", "error")
134+
except AuthApiError as exception:
135+
err = exception.to_dict()
136+
flash(err.get("message"), "error")
137+
138+
return render_template("account/identities.html",
139+
profile=profile,
140+
identities=identities,
141+
connected_identities=connected_identities,
142+
)
143+
144+
@account.route("/connect/github")
145+
@login_required
146+
def link_github():
147+
resp = supabase.auth.link_identity(
148+
{
149+
"provider": "github",
150+
"options": {"redirect_to": f"{request.host_url}auth/callback?next=account.list_identities"},
151+
}
152+
)
153+
flash(f"{'github'.title()} was successfully linked.", "info")
154+
155+
return redirect(resp.url)
156+
157+
@account.route("/connect/<provider>/disconnect")
158+
@login_required
159+
def unlink_provider(provider):
160+
try:
161+
res = supabase.auth.get_user_identities()
162+
identity = list(
163+
filter(lambda identity: identity.provider == provider, res.identities)
164+
).pop()
165+
res = supabase.auth.unlink_identity(identity)
166+
flash(f"{identity.provider.title()} was successfully unlinked.", "info")
167+
except AuthApiError as exception:
168+
err = exception.to_dict()
169+
flash(err.get("message"), "error")
170+
171+
return redirect(url_for("account.list_identities"))
112172

113173
@account.route("/delete", methods=["POST"])
114174
@login_required

app/auth.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import os
22
from flask import Blueprint, render_template, redirect, request, session, url_for, flash
3+
from supabase_auth import VerifyEmailOtpParams, VerifyTokenHashParams
34
from app.forms import AuthForm, ForgotPasswordForm, VerifyTokenForm
45
from app.supabase import supabase
56
from supabase import AuthApiError
@@ -92,14 +93,14 @@ def forgot_password():
9293
@auth.route("/confirm")
9394
def confirm():
9495
token_hash = request.args.get("token_hash")
95-
auth_type = request.args.get("type")
96+
auth_type = request.args.get("type", "email")
9697
next = request.args.get("next", "notes.home")
9798

9899
if token_hash and auth_type:
99100
if auth_type == "recovery":
100101
session["password_update_required"] = True
101102

102-
supabase.auth.verify_otp(params={"token_hash": token_hash, "type": auth_type})
103+
_ = supabase.auth.verify_otp(params=VerifyTokenHashParams(token_hash=token_hash, type=auth_type))
103104

104105
return redirect(url_for(next))
105106

@@ -110,7 +111,7 @@ def callback():
110111
next = request.args.get("next", "notes.home")
111112

112113
if code:
113-
res = supabase.auth.exchange_code_for_session({"auth_code": code})
114+
_ = supabase.auth.exchange_code_for_session({"auth_code": code})
114115

115116
return redirect(url_for(next))
116117

@@ -129,8 +130,8 @@ def verify_token():
129130
session["password_update_required"] = True
130131

131132
try:
132-
supabase.auth.verify_otp(
133-
params={"email": email, "token": token, "type": auth_type}
133+
_ = supabase.auth.verify_otp(
134+
params=VerifyEmailOtpParams(email=email, token=token, type=auth_type)
134135
)
135136
return redirect(url_for(next))
136137
except AuthApiError as exception:

static/app.css

Lines changed: 31 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -2573,6 +2573,10 @@ html {
25732573
margin-right: 1.5rem;
25742574
}
25752575

2576+
.mt-0 {
2577+
margin-top: 0px;
2578+
}
2579+
25762580
.mt-1 {
25772581
margin-top: 0.25rem;
25782582
}
@@ -2601,10 +2605,6 @@ html {
26012605
margin-top: auto;
26022606
}
26032607

2604-
.mt-0 {
2605-
margin-top: 0px;
2606-
}
2607-
26082608
.block {
26092609
display: block;
26102610
}
@@ -2764,12 +2764,6 @@ html {
27642764
margin-bottom: calc(0.5rem * var(--tw-space-y-reverse));
27652765
}
27662766

2767-
.space-y-6 > :not([hidden]) ~ :not([hidden]) {
2768-
--tw-space-y-reverse: 0;
2769-
margin-top: calc(1.5rem * calc(1 - var(--tw-space-y-reverse)));
2770-
margin-bottom: calc(1.5rem * var(--tw-space-y-reverse));
2771-
}
2772-
27732767
.space-y-4 > :not([hidden]) ~ :not([hidden]) {
27742768
--tw-space-y-reverse: 0;
27752769
margin-top: calc(1rem * calc(1 - var(--tw-space-y-reverse)));
@@ -2888,6 +2882,11 @@ html {
28882882
background-color: rgb(253 224 71 / var(--tw-bg-opacity, 1));
28892883
}
28902884

2885+
.bg-red-100 {
2886+
--tw-bg-opacity: 1;
2887+
background-color: rgb(254 226 226 / var(--tw-bg-opacity, 1));
2888+
}
2889+
28912890
.p-12 {
28922891
padding: 3rem;
28932892
}
@@ -3019,6 +3018,11 @@ html {
30193018
line-height: 1;
30203019
}
30213020

3021+
.text-base {
3022+
font-size: 1rem;
3023+
line-height: 1.5rem;
3024+
}
3025+
30223026
.text-lg {
30233027
font-size: 1.125rem;
30243028
line-height: 1.75rem;
@@ -3034,11 +3038,6 @@ html {
30343038
line-height: 1rem;
30353039
}
30363040

3037-
.text-base {
3038-
font-size: 1rem;
3039-
line-height: 1.5rem;
3040-
}
3041-
30423041
.font-bold {
30433042
font-weight: 700;
30443043
}
@@ -3088,6 +3087,11 @@ html {
30883087
color: rgb(107 114 128 / var(--tw-text-opacity, 1));
30893088
}
30903089

3090+
.text-gray-600 {
3091+
--tw-text-opacity: 1;
3092+
color: rgb(75 85 99 / var(--tw-text-opacity, 1));
3093+
}
3094+
30913095
.text-gray-800 {
30923096
--tw-text-opacity: 1;
30933097
color: rgb(31 41 55 / var(--tw-text-opacity, 1));
@@ -3123,11 +3127,6 @@ html {
31233127
color: rgb(239 68 68 / var(--tw-text-opacity, 1));
31243128
}
31253129

3126-
.text-red-600 {
3127-
--tw-text-opacity: 1;
3128-
color: rgb(220 38 38 / var(--tw-text-opacity, 1));
3129-
}
3130-
31313130
.text-white {
31323131
--tw-text-opacity: 1;
31333132
color: rgb(255 255 255 / var(--tw-text-opacity, 1));
@@ -3138,9 +3137,14 @@ html {
31383137
color: rgb(253 224 71 / var(--tw-text-opacity, 1));
31393138
}
31403139

3141-
.text-gray-600 {
3140+
.text-blue-600 {
31423141
--tw-text-opacity: 1;
3143-
color: rgb(75 85 99 / var(--tw-text-opacity, 1));
3142+
color: rgb(37 99 235 / var(--tw-text-opacity, 1));
3143+
}
3144+
3145+
.text-red-300 {
3146+
--tw-text-opacity: 1;
3147+
color: rgb(252 165 165 / var(--tw-text-opacity, 1));
31443148
}
31453149

31463150
.underline {
@@ -3213,6 +3217,11 @@ html {
32133217
color: rgb(248 113 113 / var(--tw-text-opacity, 1));
32143218
}
32153219

3220+
.hover\:text-white:hover {
3221+
--tw-text-opacity: 1;
3222+
color: rgb(255 255 255 / var(--tw-text-opacity, 1));
3223+
}
3224+
32163225
.group:hover .group-hover\:inline {
32173226
display: inline;
32183227
}
@@ -3326,10 +3335,6 @@ html {
33263335
width: 25%;
33273336
}
33283337

3329-
.\32xl\:w-11\/12 {
3330-
width: 91.666667%;
3331-
}
3332-
33333338
.\32xl\:w-5\/12 {
33343339
width: 41.666667%;
33353340
}

supabase/config.toml

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,31 @@ enable_refresh_token_rotation = true
6666
refresh_token_reuse_interval = 10
6767
# Allow/disallow new user signups to your project.
6868
enable_signup = true
69+
# Allow/disallow anonymous sign-ins to your project.
70+
enable_anonymous_sign_ins = true
71+
# Allow/disallow testing manual linking of accounts
72+
enable_manual_linking = true
73+
# Passwords shorter than this value will be rejected as weak. Minimum 6, recommended 8 or more.
74+
minimum_password_length = 6
75+
# Passwords that do not meet the following requirements will be rejected as weak. Supported values
76+
# are: `letters_digits`, `lower_upper_letters_digits`, `lower_upper_letters_digits_symbols`
77+
password_requirements = ""
78+
79+
[auth.rate_limit]
80+
# Number of emails that can be sent per hour. Requires auth.email.smtp to be enabled.
81+
email_sent = 2
82+
# Number of SMS messages that can be sent per hour. Requires auth.sms to be enabled.
83+
sms_sent = 30
84+
# Number of anonymous sign-ins that can be made per hour per IP address. Requires enable_anonymous_sign_ins = true.
85+
anonymous_users = 30
86+
# Number of sessions that can be refreshed in a 5 minute interval per IP address.
87+
token_refresh = 150
88+
# Number of sign up and sign-in requests that can be made in a 5 minute interval per IP address (excludes anonymous users).
89+
sign_in_sign_ups = 30
90+
# Number of OTP / Magic link verifications that can be made in a 5 minute interval per IP address.
91+
token_verifications = 30
92+
# Number of Web3 logins that can be made in a 5 minute interval per IP address.
93+
web3 = 30
6994

7095
[auth.email]
7196
# Allow/disallow new user signups via email to your project.

templates/account/identities.html

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
{% extends "layout.html" %}
2+
{% import "_helpers.html" as h %}
3+
{% set active_page = "account.home" %}
4+
5+
{% block title %}Connect Social{% endblock %}
6+
7+
{% block content %}
8+
<div class="w-11/12 p-12 px-6 py-10 lg:w-full 2xl:w-5/12 sm:px-10 sm:py-6">
9+
<h2 class="font-semibold text-2xl mb-4">Connect Social</h2>
10+
<p class="font-medium mb-10">
11+
Hi {{ profile.display_name or user.email }}, you can update your social auth from here
12+
</p>
13+
14+
{{ h.alert('mb-10') }}
15+
<ul class="divide-y-2 divide-gray-200">
16+
{% if "email" in connected_identities %}
17+
<li class="flex gap-2 items-center justify-between py-2">
18+
<h3 class="grow text-base leading-6 font-semibold">Email</h3>
19+
<p class="block p-3 text-blue-600">Connected</p>
20+
</li>
21+
{% endif %}
22+
<li class="flex gap-2 items-center justify-between py-2">
23+
<h3 class="grow text-base leading-6 font-semibold">Github</h3>
24+
{% if connected_identities | length < 2 %}
25+
<p class="block p-3 text-blue-600">Connected</p>
26+
{% else %}
27+
{% if "github" in connected_identities %}
28+
<a class="block p-3 rounded-lg border border-red-300 text-red-300 hover:bg-red-600 hover:text-white" href="{{ url_for('account.unlink_provider', provider='github') }}">Disconnect</a>
29+
{% else %}
30+
<a class="block p-3 hover:bg-blue-600 hover:text-blue-200" href="{{ url_for('account.link_github') }}">Connect</a>
31+
{% endif %}
32+
{% endif %}
33+
</li>
34+
</ul>
35+
</div>
36+
{% endblock %}

templates/account/index.html

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,16 @@ <h2 class="font-semibold text-2xl mb-4">Account</h2>
1414
{{ h.alert('mb-10') }}
1515
<ul class="divide-y-2 divide-gray-200">
1616
<li class="flex justify-between hover:bg-blue-600 hover:text-blue-200">
17-
<a class="block w-full p-3" href="/account/update">Update</a>
17+
<a class="block w-full p-3" href="{{ url_for('account.update') }}">Update</a>
1818
</li>
1919
<li class="flex justify-between hover:bg-blue-600 hover:text-blue-200">
20-
<a class="block w-full p-3" href="/account/update-email">Update email</a>
20+
<a class="block w-full p-3" href="{{ url_for('account.update_email') }}">Update email</a>
2121
</li>
2222
<li class="flex justify-between hover:bg-blue-600 hover:text-blue-200">
23-
<a class="block w-full p-3" href="/account/update-password">Update password</a>
23+
<a class="block w-full p-3" href="{{ url_for('account.update_password') }}">Update password</a>
24+
</li>
25+
<li class="flex justify-between hover:bg-blue-600 hover:text-blue-200">
26+
<a class="block w-full p-3" href="{{ url_for('account.list_identities') }}">Connect social</a>
2427
</li>
2528
</ul>
2629
<div class="flex flex-col justify-between border border-red-300 rounded-md p-4 mt-10">

0 commit comments

Comments
 (0)