Skip to content

Commit 1ac0a3c

Browse files
committed
[examples/hackernews] GitHub OAuth login flow example.
The OAuth client id (resp. secret) is loaded from `examples/hackernews/gh_oauth_client_id.txt` (resp. `examples/hackernews/gh_oauth_secret.txt`).
1 parent 0f15986 commit 1ac0a3c

File tree

3 files changed

+102
-0
lines changed

3 files changed

+102
-0
lines changed

examples/hackernews/compose.yml

+9
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ services:
1414
web:
1515
image: reactive-hackernews/web-service
1616
build: ./web_service
17+
secrets:
18+
- gh_oauth_client_id
19+
- gh_oauth_secret
1720
depends_on:
1821
db:
1922
condition: service_healthy
@@ -41,3 +44,9 @@ services:
4144

4245
volumes:
4346
pgdata:
47+
48+
secrets:
49+
gh_oauth_client_id:
50+
file: ./gh_oauth_client_id.txt
51+
gh_oauth_secret:
52+
file: ./gh_oauth_secret.txt

examples/hackernews/web_service/app.py

+73
Original file line numberDiff line numberDiff line change
@@ -273,3 +273,76 @@ def simulate_activity():
273273
time.sleep(1)
274274

275275
return "ok", 200
276+
277+
278+
# See https://docs.docker.com/compose/how-tos/use-secrets/
279+
with open("/run/secrets/gh_oauth_client_id") as f:
280+
GH_OAUTH_CLIENT_ID = f.read().strip()
281+
with open("/run/secrets/gh_oauth_secret") as f:
282+
GH_OAUTH_CLIENT_SECRET = f.read().strip()
283+
GH_OAUTH_ACCESS_TOKEN_URI = "https://github.com/login/oauth/access_token"
284+
GH_OAUTH_REDIRECT_URI = "https://localhost/api/oauth"
285+
GH_USER_API_URI = "https://api.github.com/user"
286+
287+
288+
@app.get("/oauth")
289+
def oauth_callback():
290+
code = request.args["code"]
291+
resp = requests.post(
292+
GH_OAUTH_ACCESS_TOKEN_URI,
293+
headers={
294+
"Accept": "application/json",
295+
},
296+
json={
297+
"client_id": GH_OAUTH_CLIENT_ID,
298+
"client_secret": GH_OAUTH_CLIENT_SECRET,
299+
"code": code,
300+
"redirect_uri": GH_OAUTH_REDIRECT_URI,
301+
},
302+
)
303+
304+
session["gh_access_token"] = resp.json()["access_token"]
305+
resp = requests.get(
306+
f"{GH_USER_API_URI}",
307+
headers={
308+
"Accept": "application/json",
309+
"Authorization": f'Bearer {session["gh_access_token"]}',
310+
},
311+
)
312+
gh_user = resp.json()
313+
314+
with get_db() as db:
315+
with db.cursor() as cur:
316+
cur.execute(
317+
"SELECT id, name, email FROM users WHERE LOWER(email) = LOWER(%s)",
318+
(gh_user["email"],),
319+
)
320+
if cur.rowcount < 1:
321+
cur.execute(
322+
"INSERT INTO users(name, email) VALUES (%s, %s) RETURNING id",
323+
(gh_user["login"], gh_user["email"]),
324+
)
325+
db.commit()
326+
user_session = {
327+
"user_id": cur.fetchone()[0],
328+
"name": gh_user["login"],
329+
"email": gh_user["email"],
330+
}
331+
else:
332+
user = cur.fetchone()
333+
user_session = {
334+
"user_id": user[0],
335+
"name": user[1],
336+
"email": user[2],
337+
}
338+
339+
# Store the user_id in the Flask session to avoid a round-trip when upvoting.
340+
session["user_id"] = user_session["user_id"]
341+
342+
# TODO: Error handling.
343+
requests.patch(
344+
f"{REACTIVE_SERVICE_URL}/inputs/sessions",
345+
json=[[session["session_id"], [user_session]]],
346+
)
347+
348+
return redirect("/")

examples/hackernews/www/src/App.tsx

+20
Original file line numberDiff line numberDiff line change
@@ -332,6 +332,14 @@ function Login() {
332332
});
333333
}
334334

335+
function navigateToGitHubOAuth() {
336+
const OAUTH_GH_CLIENT_ID = "Ov23ctSiS46qzKAEarWy";
337+
const OAUTH_GH_REDIRECT_URI = "https://localhost/api/oauth";
338+
const OAUTH_GH_SCOPE = "user:read";
339+
// TODO: Anti-CSRF token passed through `state` uri param.
340+
window.location.href = `https://github.com/login/oauth/authorize?client_id=${OAUTH_GH_CLIENT_ID}&redirect_uri=${OAUTH_GH_REDIRECT_URI}&scope=${OAUTH_GH_SCOPE}`;
341+
}
342+
335343
return (
336344
<>
337345
<Header session={null} />
@@ -358,6 +366,18 @@ function Login() {
358366
/>
359367
<button type="submit">Login</button>
360368
</form>
369+
<button onClick={navigateToGitHubOAuth} style={{ display: "flex" }}>
370+
<svg
371+
xmlns="http://www.w3.org/2000/svg"
372+
width="20"
373+
height="20"
374+
fill="currentColor"
375+
viewBox="0 0 1792 1792"
376+
>
377+
<path d="M896 128q209 0 385.5 103t279.5 279.5 103 385.5q0 251-146.5 451.5t-378.5 277.5q-27 5-40-7t-13-30q0-3 .5-76.5t.5-134.5q0-97-52-142 57-6 102.5-18t94-39 81-66.5 53-105 20.5-150.5q0-119-79-206 37-91-8-204-28-9-81 11t-92 44l-38 24q-93-26-192-26t-192 26q-16-11-42.5-27t-83.5-38.5-85-13.5q-45 113-8 204-79 87-79 206 0 85 20.5 150t52.5 105 80.5 67 94 39 102.5 18q-39 36-49 103-21 10-45 15t-57 5-65.5-21.5-55.5-62.5q-19-32-48.5-52t-49.5-24l-20-3q-21 0-29 4.5t-5 11.5 9 14 13 12l7 5q22 10 43.5 38t31.5 51l10 23q13 38 44 61.5t67 30 69.5 7 55.5-3.5l23-4q0 38 .5 88.5t.5 54.5q0 18-13 30t-40 7q-232-77-378.5-277.5t-146.5-451.5q0-209 103-385.5t279.5-279.5 385.5-103zm-477 1103q3-7-7-12-10-3-13 2-3 7 7 12 9 6 13-2zm31 34q7-5-2-16-10-9-16-3-7 5 2 16 10 10 16 3zm30 45q9-7 0-19-8-13-17-6-9 5 0 18t17 7zm42 42q8-8-4-19-12-12-20-3-9 8 4 19 12 12 20 3zm57 25q3-11-13-16-15-4-19 7t13 15q15 6 19-6zm63 5q0-13-17-11-16 0-16 11 0 13 17 11 16 0 16-11zm58-10q-2-11-18-9-16 3-14 15t18 8 14-14z"></path>
378+
</svg>
379+
<div style={{ padding: "0 8px" }}>Login with GitHub</div>
380+
</button>
361381
</>
362382
);
363383
}

0 commit comments

Comments
 (0)