Skip to content

Commit 6a0bece

Browse files
committed
Add external_auth_support
1 parent d9fb743 commit 6a0bece

File tree

12 files changed

+323
-70
lines changed

12 files changed

+323
-70
lines changed

docker-compose.yml

+4-4
Original file line numberDiff line numberDiff line change
@@ -31,19 +31,19 @@ services:
3131
POSTGRES_USER: ${CODEBATTLE_DB_USERNAME}
3232
POSTGRES_PASSWORD: ${CODEBATTLE_DB_PASSWORD}
3333
volumes:
34-
- pg_data2:/var/lib/postgresql/data
34+
- pg_data:/var/lib/postgresql/data
3535

3636
# same db but forwards port to host
3737
db-local:
38-
image: postgres:12-alpine
38+
image: postgres:16-alpine
3939
ports:
4040
- "5432:${CODEBATTLE_DB_PORT}"
4141
environment:
4242
POSTGRES_USER: ${CODEBATTLE_DB_USERNAME}
4343
POSTGRES_PASSWORD: ${CODEBATTLE_DB_PASSWORD}
4444
command: postgres -c 'max_connections=1000'
4545
volumes:
46-
- pg_data2:/var/lib/postgresql/data
46+
- pg_data:/var/lib/postgresql/data
4747

4848
volumes:
49-
pg_data2:
49+
pg_data:
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
defmodule Codebattle.Auth.External do
2+
@moduledoc """
3+
Module that handles External OAuth
4+
"""
5+
6+
def client_id do
7+
Application.get_env(:codebattle, :oauth)[:external_client_id]
8+
end
9+
10+
def client_secret do
11+
Application.get_env(:codebattle, :oauth)[:external_client_secret]
12+
end
13+
14+
def external_auth(code, redirect_uri) do
15+
body =
16+
URI.encode_query(%{
17+
grant_type: "authorization_code",
18+
code: code,
19+
client_id: client_id(),
20+
client_secret: client_secret(),
21+
redirect_uri: redirect_uri
22+
})
23+
24+
headers = [{"Content-Type", "application/x-www-form-urlencoded"}]
25+
26+
opts =
27+
Keyword.merge(
28+
Application.get_env(:codebattle, :auth_req_options, []),
29+
body: body,
30+
headers: headers
31+
)
32+
33+
external_auth_url()
34+
|> Req.post!(opts)
35+
|> Map.get(:body)
36+
|> check_authenticated()
37+
end
38+
39+
defp external_auth_url do
40+
Application.get_env(:codebattle, :oauth)[:external_auth_url]
41+
end
42+
43+
defp check_authenticated(%{"access_token" => access_token}) do
44+
get_user_details(access_token)
45+
end
46+
47+
defp check_authenticated(error), do: {:error, error}
48+
49+
defp get_user_details(access_token) do
50+
opts =
51+
Keyword.put(
52+
Application.get_env(:codebattle, :auth_req_options, []),
53+
:headers,
54+
authorization: "OAuth #{access_token}"
55+
)
56+
57+
:codebattle
58+
|> Application.get_env(:oauth)
59+
|> Keyword.get(:external_user_info_url)
60+
|> Req.get!(opts)
61+
|> Map.get(:body)
62+
|> Map.take(["default_avatar_id", "id", "is_avatar_empty"])
63+
|> Runner.AtomizedMap.atomize()
64+
|> dbg()
65+
end
66+
end
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
defmodule Codebattle.Auth.User.ExternalUser do
2+
@moduledoc """
3+
Retrieve user information from externalt oauth request
4+
"""
5+
6+
alias Codebattle.Repo
7+
alias Codebattle.User
8+
9+
@spec find_or_create(map()) :: {:ok, User.t()} | {:error, term()}
10+
def find_or_create(profile) do
11+
User
12+
|> Repo.get_by(external_oauth_id: profile.id)
13+
|> case do
14+
nil ->
15+
name = "External-#{profile.id}"
16+
17+
params = %{
18+
external_oauth_id: profile.id,
19+
name: name,
20+
subscription_type: :free,
21+
lang: Application.get_env(:codebattle, :default_lang_slug),
22+
avatar_url: external_avatar_url(profile)
23+
}
24+
25+
%User{}
26+
|> User.changeset(params)
27+
|> Repo.insert()
28+
29+
user ->
30+
params = %{avatar_url: external_avatar_url(profile)}
31+
32+
user
33+
|> User.changeset(params)
34+
|> Repo.update()
35+
end
36+
end
37+
38+
defp external_avatar_url(%{is_avatar_empty: true}), do: nil
39+
40+
defp external_avatar_url(profile) do
41+
:codebattle
42+
|> Application.get_env(:oauth)
43+
|> Keyword.get(:external_avatar_url_template)
44+
|> String.replace(~r/AVATAR_ID/, profile.default_avatar_id)
45+
end
46+
end

services/app/apps/codebattle/lib/codebattle/user.ex

+6-1
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ defmodule Codebattle.User do
2323
only: [
2424
:achievements,
2525
:avatar_url,
26+
:category,
2627
:clan,
2728
:clan_id,
2829
:editor_mode,
@@ -49,6 +50,7 @@ defmodule Codebattle.User do
4950
field(:achievements, {:array, :string}, default: [])
5051
field(:auth_token, :string)
5152
field(:avatar_url, :string)
53+
field(:category, :string)
5254
field(:clan, :string)
5355
field(:clan_id, :integer)
5456
field(:collab_logo, :string)
@@ -58,6 +60,7 @@ defmodule Codebattle.User do
5860
field(:editor_mode, :string)
5961
field(:editor_theme, :string)
6062
field(:email, :string)
63+
field(:external_oauth_id, :string)
6164
field(:firebase_uid, :string)
6265
field(:github_id, :integer)
6366
field(:github_name, :string)
@@ -88,12 +91,14 @@ defmodule Codebattle.User do
8891
:achievements,
8992
:auth_token,
9093
:avatar_url,
94+
:category,
9195
:discord_avatar,
9296
:discord_id,
9397
:discord_name,
9498
:editor_mode,
9599
:editor_theme,
96100
:email,
101+
:external_oauth_id,
97102
:firebase_uid,
98103
:github_id,
99104
:github_name,
@@ -120,7 +125,7 @@ defmodule Codebattle.User do
120125

121126
def token_changeset(user, params \\ %{}) do
122127
user
123-
|> cast(params, [:auth_token, :name, :clan, :subscription_type])
128+
|> cast(params, [:external_oauth_id, :category, :name, :clan, :subscription_type])
124129
|> cast_embed(:sound_settings)
125130
|> unique_constraint(:name)
126131
|> validate_length(:name, min: 2, max: 39)

services/app/apps/codebattle/lib/codebattle_web/controllers/auth_controller.ex

+6-2
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ defmodule CodebattleWeb.AuthController do
33
use Gettext, backend: CodebattleWeb.Gettext
44

55
alias Codebattle.Auth.Discord
6+
alias Codebattle.Auth.External
67
alias Codebattle.Auth.Github
78

89
require Logger
@@ -50,12 +51,10 @@ defmodule CodebattleWeb.AuthController do
5051
case provider_name do
5152
"github" ->
5253
oauth_github_url = Github.login_url(%{redirect_uri: redirect_uri})
53-
5454
redirect(conn, external: oauth_github_url)
5555

5656
"discord" ->
5757
oauth_discord_url = Discord.login_url(%{redirect_uri: redirect_uri})
58-
5958
redirect(conn, external: oauth_discord_url)
6059

6160
_ ->
@@ -85,6 +84,11 @@ defmodule CodebattleWeb.AuthController do
8584
redirect_uri = Routes.auth_url(conn, :callback, provider_name)
8685
{:ok, profile} = Discord.discord_auth(code, redirect_uri)
8786
Codebattle.Auth.User.DiscordUser.find_or_create(profile)
87+
88+
"external" ->
89+
redirect_uri = Routes.auth_url(conn, :callback, provider_name)
90+
profile = External.external_auth(code, redirect_uri)
91+
Codebattle.Auth.User.ExternalUser.find_or_create(profile)
8892
end
8993

9094
case case_result do

0 commit comments

Comments
 (0)