Skip to content

Commit 84d293d

Browse files
authored
Merge branch 'master' into payload_size_limit
2 parents aeb717c + f5f8890 commit 84d293d

File tree

7 files changed

+189
-12
lines changed

7 files changed

+189
-12
lines changed

config/config.exs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,9 @@ config :cadet, Cadet.Jobs.Scheduler,
2525
# Compute rolling leaderboard every 2 hours
2626
{"0 */2 * * *", {Cadet.Assessments, :update_rolling_contest_leaderboards, []}},
2727
# Collate contest entries that close in the previous day at 00:01
28-
{"1 0 * * *", {Cadet.Assessments, :update_final_contest_entries, []}}
28+
{"1 0 * * *", {Cadet.Assessments, :update_final_contest_entries, []}},
29+
# Clean up expired exchange tokens at 00:01
30+
{"1 0 * * *", {Cadet.TokenExchange, :delete_expired, []}}
2931
]
3032

3133
# Configures the endpoint

config/dev.secrets.exs.example

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ config :cadet,
3636
# %{
3737
# assertion_extractor: Cadet.Auth.Providers.NusstuAssertionExtractor,
3838
# client_redirect_url: "http://cadet.frontend:8000/login/callback"
39+
# vscode_redirect_url_prefix: "vscode://source-academy.source-academy/sso",
40+
# client_post_exchange_redirect_url: "http://cadet.frontend:8000/login/vscode_callback",
3941
# }},
4042

4143
"test" =>

lib/cadet/code_exchange.ex

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
defmodule Cadet.TokenExchange do
2+
@moduledoc """
3+
The TokenExchange entity stores short-lived codes to be exchanged for long-lived auth tokens.
4+
"""
5+
use Cadet, :model
6+
7+
import Ecto.Query
8+
9+
alias Cadet.Repo
10+
alias Cadet.Accounts.User
11+
12+
@primary_key {:code, :string, []}
13+
schema "token_exchange" do
14+
field(:generated_at, :utc_datetime_usec)
15+
field(:expires_at, :utc_datetime_usec)
16+
17+
belongs_to(:user, User)
18+
19+
timestamps()
20+
end
21+
22+
@required_fields ~w(code generated_at expires_at user_id)a
23+
24+
def get_by_code(code) do
25+
case Repo.get_by(__MODULE__, code: code) do
26+
nil ->
27+
{:error, "Not found"}
28+
29+
struct ->
30+
if Timex.before?(struct.expires_at, Timex.now()) do
31+
{:error, "Expired"}
32+
else
33+
struct = Repo.preload(struct, :user)
34+
Repo.delete(struct)
35+
{:ok, struct}
36+
end
37+
end
38+
end
39+
40+
def delete_expired do
41+
now = Timex.now()
42+
43+
Repo.delete_all(from(c in __MODULE__, where: c.expires_at < ^now))
44+
end
45+
46+
def changeset(struct, attrs) do
47+
struct
48+
|> cast(attrs, @required_fields)
49+
|> validate_required(@required_fields)
50+
end
51+
52+
def insert(attrs) do
53+
changeset =
54+
%__MODULE__{}
55+
|> changeset(attrs)
56+
57+
changeset
58+
|> Repo.insert()
59+
end
60+
end

lib/cadet_web/controllers/auth_controller.ex

Lines changed: 104 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@ defmodule CadetWeb.AuthController do
55
use CadetWeb, :controller
66
use PhoenixSwagger
77

8-
alias Cadet.Accounts
9-
alias Cadet.Accounts.User
8+
alias Cadet.{Accounts, Accounts.User}
109
alias Cadet.Auth.{Guardian, Provider}
10+
alias Cadet.TokenExchange
1111

1212
@doc """
1313
Receives a /login request with valid attributes.
@@ -85,9 +85,87 @@ defmodule CadetWeb.AuthController do
8585
send_resp(conn, :bad_request, "Missing parameter")
8686
end
8787

88-
@spec create_user_and_tokens(Provider.authorise_params()) ::
89-
{:ok, %{access_token: String.t(), refresh_token: String.t()}} | Plug.Conn.t()
90-
defp create_user_and_tokens(
88+
@doc """
89+
Exchanges a short-lived code for access and refresh tokens.
90+
"""
91+
def exchange(
92+
conn,
93+
%{
94+
"code" => code,
95+
"provider" => provider
96+
}
97+
) do
98+
case TokenExchange.get_by_code(code) do
99+
{:error, _message} ->
100+
conn
101+
|> put_status(:forbidden)
102+
|> text("Invalid code")
103+
104+
{:ok, struct} ->
105+
tokens = generate_tokens(struct.user)
106+
107+
{_provider, %{client_post_exchange_redirect_url: client_post_exchange_redirect_url}} =
108+
Application.get_env(:cadet, :identity_providers, %{})[provider]
109+
110+
conn
111+
|> put_resp_header(
112+
"location",
113+
URI.encode(
114+
client_post_exchange_redirect_url <>
115+
"?access_token=" <> tokens.access_token <> "&refresh_token=" <> tokens.refresh_token
116+
)
117+
)
118+
|> send_resp(302, "")
119+
|> halt()
120+
end
121+
end
122+
123+
@doc """
124+
Alternate callback URL which redirect to VSCode via deeplinking.
125+
"""
126+
def saml_redirect_vscode(
127+
conn,
128+
%{
129+
"provider" => provider
130+
}
131+
) do
132+
code_ttl = 60
133+
134+
case create_user(%{
135+
conn: conn,
136+
provider_instance: provider,
137+
code: nil,
138+
client_id: nil,
139+
redirect_uri: nil
140+
}) do
141+
{:ok, user} ->
142+
code = generate_code()
143+
144+
TokenExchange.insert(%{
145+
code: code,
146+
generated_at: Timex.now(),
147+
expires_at: Timex.add(Timex.now(), Timex.Duration.from_seconds(code_ttl)),
148+
user_id: user.id
149+
})
150+
151+
{_provider, %{vscode_redirect_url_prefix: vscode_redirect_url_prefix}} =
152+
Application.get_env(:cadet, :identity_providers, %{})[provider]
153+
154+
conn
155+
|> put_resp_header(
156+
"location",
157+
vscode_redirect_url_prefix <> "?provider=" <> provider <> "&code=" <> code
158+
)
159+
|> send_resp(302, "")
160+
|> halt()
161+
162+
conn ->
163+
conn
164+
end
165+
end
166+
167+
@spec create_user(Provider.authorise_params()) :: {:ok, User.t()} | Plug.Conn.t()
168+
defp create_user(
91169
params = %{
92170
conn: conn,
93171
provider_instance: provider
@@ -96,7 +174,7 @@ defmodule CadetWeb.AuthController do
96174
with {:authorise, {:ok, %{token: token, username: username}}} <-
97175
{:authorise, Provider.authorise(params)},
98176
{:signin, {:ok, user}} <- {:signin, Accounts.sign_in(username, token, provider)} do
99-
{:ok, generate_tokens(user)}
177+
{:ok, user}
100178
else
101179
{:authorise, {:error, :upstream, reason}} ->
102180
conn
@@ -121,6 +199,18 @@ defmodule CadetWeb.AuthController do
121199
end
122200
end
123201

202+
@spec create_user_and_tokens(Provider.authorise_params()) ::
203+
{:ok, %{access_token: String.t(), refresh_token: String.t()}} | Plug.Conn.t()
204+
defp create_user_and_tokens(params) do
205+
case create_user(params) do
206+
{:ok, user} ->
207+
{:ok, generate_tokens(user)}
208+
209+
conn ->
210+
conn
211+
end
212+
end
213+
124214
@doc """
125215
Receives a /refresh request with valid attribute.
126216
@@ -170,6 +260,14 @@ defmodule CadetWeb.AuthController do
170260
%{access_token: access_token, refresh_token: refresh_token}
171261
end
172262

263+
@spec generate_code :: String.t()
264+
defp generate_code do
265+
16
266+
|> :crypto.strong_rand_bytes()
267+
|> Base.url_encode64(padding: false)
268+
|> String.slice(0, 22)
269+
end
270+
173271
swagger_path :create do
174272
post("/auth/login")
175273

lib/cadet_web/router.ex

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,8 @@ defmodule CadetWeb.Router do
5151
post("/auth/login", AuthController, :create)
5252
post("/auth/logout", AuthController, :logout)
5353
get("/auth/saml_redirect", AuthController, :saml_redirect)
54+
get("/auth/saml_redirect_vscode", AuthController, :saml_redirect_vscode)
55+
get("/auth/exchange", AuthController, :exchange)
5456
end
5557

5658
scope "/v2", CadetWeb do

mix.lock

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
"bunt": {:hex, :bunt, "0.2.1", "e2d4792f7bc0ced7583ab54922808919518d0e57ee162901a16a1b6664ef3b14", [:mix], [], "hexpm", "a330bfb4245239787b15005e66ae6845c9cd524a288f0d141c148b02603777a5"},
1010
"bypass": {:hex, :bypass, "2.1.0", "909782781bf8e20ee86a9cabde36b259d44af8b9f38756173e8f5e2e1fabb9b1", [:mix], [{:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.0", [hex: :plug_cowboy, repo: "hexpm", optional: false]}, {:ranch, "~> 1.3", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "d9b5df8fa5b7a6efa08384e9bbecfe4ce61c77d28a4282f79e02f1ef78d96b80"},
1111
"castore": {:hex, :castore, "1.0.12", "053f0e32700cbec356280c0e835df425a3be4bc1e0627b714330ad9d0f05497f", [:mix], [], "hexpm", "3dca286b2186055ba0c9449b4e95b97bf1b57b47c1f2644555879e659960c224"},
12-
"certifi": {:hex, :certifi, "2.14.0", "ed3bef654e69cde5e6c022df8070a579a79e8ba2368a00acf3d75b82d9aceeed", [:rebar3], [], "hexpm", "ea59d87ef89da429b8e905264fdec3419f84f2215bb3d81e07a18aac919026c3"},
12+
"certifi": {:hex, :certifi, "2.15.0", "0e6e882fcdaaa0a5a9f2b3db55b1394dba07e8d6d9bcad08318fb604c6839712", [:rebar3], [], "hexpm", "b147ed22ce71d72eafdad94f055165c1c182f61a2ff49df28bcc71d1d5b94a60"},
1313
"combine": {:hex, :combine, "0.10.0", "eff8224eeb56498a2af13011d142c5e7997a80c8f5b97c499f84c841032e429f", [:mix], [], "hexpm", "1b1dbc1790073076580d0d1d64e42eae2366583e7aecd455d1215b0d16f2451b"},
1414
"configparser_ex": {:hex, :configparser_ex, "4.0.0", "17e2b831cfa33a08c56effc610339b2986f0d82a9caa0ed18880a07658292ab6", [:mix], [], "hexpm", "02e6d1a559361a063cba7b75bc3eb2d6ad7e62730c551cc4703541fd11e65e5b"},
1515
"connection": {:hex, :connection, "1.1.0", "ff2a49c4b75b6fb3e674bfc5536451607270aac754ffd1bdfe175abe4a6d7a68", [:mix], [], "hexpm", "722c1eb0a418fbe91ba7bd59a47e28008a189d47e37e0e7bb85585a016b2869c"},
@@ -31,7 +31,7 @@
3131
"erlex": {:hex, :erlex, "0.2.7", "810e8725f96ab74d17aac676e748627a07bc87eb950d2b83acd29dc047a30595", [:mix], [], "hexpm", "3ed95f79d1a844c3f6bf0cea61e0d5612a42ce56da9c03f01df538685365efb0"},
3232
"esaml": {:hex, :esaml, "4.6.0", "8fb5a3a0d56ccfce3e081a2f72b29058511047a2abbafb64cb6f595bf7465124", [:rebar3], [{:cowboy, "< 3.0.0", [hex: :cowboy, repo: "hexpm", optional: false]}], "hexpm", "d34d0b259cd8ac8215fd2c333fac9dbbb91b5f5da5a9304508612ff3ac0afa7a"},
3333
"ex2ms": {:hex, :ex2ms, "1.7.0", "45b9f523d0b777667ded60070d82d871a37e294f0b6c5b8eca86771f00f82ee1", [:mix], [], "hexpm", "2589eee51f81f1b1caa6d08c990b1ad409215fe6f64c73f73c67d36ed10be827"},
34-
"ex_aws": {:hex, :ex_aws, "2.5.8", "0393cfbc5e4a9e7017845451a015d836a670397100aa4c86901980e2a2c5f7d4", [:mix], [{:configparser_ex, "~> 4.0", [hex: :configparser_ex, repo: "hexpm", optional: true]}, {:hackney, "~> 1.16", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: true]}, {:jsx, "~> 2.8 or ~> 3.0", [hex: :jsx, repo: "hexpm", optional: true]}, {:mime, "~> 1.2 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:req, "~> 0.3", [hex: :req, repo: "hexpm", optional: true]}, {:sweet_xml, "~> 0.7", [hex: :sweet_xml, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "8f79777b7932168956c8cc3a6db41f5783aa816eb50de356aed3165a71e5f8c3"},
34+
"ex_aws": {:hex, :ex_aws, "2.5.9", "8e2455172f0e5cbe2f56dd68de514f0dae6bb26d6b6e2f435a06434cf9dbb412", [:mix], [{:configparser_ex, "~> 4.0", [hex: :configparser_ex, repo: "hexpm", optional: true]}, {:hackney, "~> 1.16", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: true]}, {:jsx, "~> 2.8 or ~> 3.0", [hex: :jsx, repo: "hexpm", optional: true]}, {:mime, "~> 1.2 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:req, "~> 0.5.10 or ~> 0.6 or ~> 1.0", [hex: :req, repo: "hexpm", optional: true]}, {:sweet_xml, "~> 0.7", [hex: :sweet_xml, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "cbdb6ffb0e6c6368de05ed8641fe1376298ba23354674428e5b153a541f23359"},
3535
"ex_aws_lambda": {:hex, :ex_aws_lambda, "2.1.0", "f28bffae6dde34ba17ef815ef50a82a1e7f3af26d36fe31dbda3a7657e0de449", [:mix], [{:ex_aws, "~> 2.0", [hex: :ex_aws, repo: "hexpm", optional: false]}], "hexpm", "25608630b8b45fe22b0237696662f88be724bc89f77ee42708a5871511f531a0"},
3636
"ex_aws_s3": {:hex, :ex_aws_s3, "2.4.0", "ce8decb6b523381812798396bc0e3aaa62282e1b40520125d1f4eff4abdff0f4", [:mix], [{:ex_aws, "~> 2.0", [hex: :ex_aws, repo: "hexpm", optional: false]}, {:sweet_xml, ">= 0.0.0", [hex: :sweet_xml, repo: "hexpm", optional: true]}], "hexpm", "85dda6e27754d94582869d39cba3241d9ea60b6aa4167f9c88e309dc687e56bb"},
3737
"ex_aws_secretsmanager": {:hex, :ex_aws_secretsmanager, "2.0.0", "deff8c12335f0160882afeb9687e55a97fddcd7d9a82fc3a6fbb270797374773", [:mix], [{:ex_aws, "~> 2.0", [hex: :ex_aws, repo: "hexpm", optional: false]}], "hexpm", "8b2838af536c32263ff797012b29e87bad73ef34f43cfa60ebca8e84576f6d45"},
@@ -53,7 +53,7 @@
5353
"git_hooks": {:hex, :git_hooks, "0.8.1", "1f6a1b065638e07ed89a49804dac6c24d8ac8d27c8f9fd0e9620d5bef8c30f41", [:mix], [{:recase, "~> 0.8.0", [hex: :recase, repo: "hexpm", optional: false]}], "hexpm", "267d8b82615ad439177b2a4bc2efadb7491ec1c8520dacc67ddc38c251448cbc"},
5454
"guardian": {:hex, :guardian, "2.3.2", "78003504b987f2b189d76ccf9496ceaa6a454bb2763627702233f31eb7212881", [:mix], [{:jose, "~> 1.8", [hex: :jose, repo: "hexpm", optional: false]}, {:plug, "~> 1.3.3 or ~> 1.4", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "b189ff38cd46a22a8a824866a6867ca8722942347f13c33f7d23126af8821b52"},
5555
"guardian_db": {:hex, :guardian_db, "2.1.0", "ec95a9d99cdd1e550555d09a7bb4a340d8887aad0697f594590c2fd74be02426", [:mix], [{:ecto, "~> 3.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.1", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:guardian, "~> 1.0 or ~> 2.0", [hex: :guardian, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.13", [hex: :postgrex, repo: "hexpm", optional: true]}], "hexpm", "f8e7d543ac92c395f3a7fd5acbe6829faeade57d688f7562e2f0fca8f94a0d70"},
56-
"hackney": {:hex, :hackney, "1.23.0", "55cc09077112bcb4a69e54be46ed9bc55537763a96cd4a80a221663a7eafd767", [:rebar3], [{:certifi, "~> 2.14.0", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "~> 6.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "~> 1.0.0", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~> 1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.4.1", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~> 1.1.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}, {:unicode_util_compat, "~> 0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "6cd1c04cd15c81e5a493f167b226a15f0938a84fc8f0736ebe4ddcab65c0b44e"},
56+
"hackney": {:hex, :hackney, "1.24.1", "f5205a125bba6ed4587f9db3cc7c729d11316fa8f215d3e57ed1c067a9703fa9", [:rebar3], [{:certifi, "~> 2.15.0", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "~> 6.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "~> 1.0.0", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~> 1.4", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.4.1", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~> 1.1.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}, {:unicode_util_compat, "~> 0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "f4a7392a0b53d8bbc3eb855bdcc919cd677358e65b2afd3840b5b3690c4c8a39"},
5757
"httpoison": {:hex, :httpoison, "1.8.2", "9eb9c63ae289296a544842ef816a85d881d4a31f518a0fec089aaa744beae290", [:mix], [{:hackney, "~> 1.17", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "2bb350d26972e30c96e2ca74a1aaf8293d61d0742ff17f01e0279fef11599921"},
5858
"idna": {:hex, :idna, "6.1.1", "8a63070e9f7d0c62eb9d9fcb360a7de382448200fbbd1b106cc96d3d8099df8d", [:rebar3], [{:unicode_util_compat, "~> 0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "92376eb7894412ed19ac475e4a86f7b413c1b9fbb5bd16dccd57934157944cea"},
5959
"inch_ex": {:hex, :inch_ex, "2.1.0-rc.1", "7642a8902c0d2ed5d9b5754b2fc88fedf630500d630fc03db7caca2e92dedb36", [:mix], [{:bunt, "~> 0.2", [hex: :bunt, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "4ceee988760f9382d1c1d0b93ea5875727f6071693e89a0a3c49c456ef1be75d"},
@@ -65,7 +65,7 @@
6565
"memento": {:hex, :memento, "0.3.2", "38cfc8ff9bcb1adff7cbd0f3b78a762636b86dff764729d1c82d0464c539bdd0", [:mix], [], "hexpm", "25cf691a98a0cb70262f4a7543c04bab24648cb2041d937eb64154a8d6f8012b"},
6666
"metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"},
6767
"mime": {:hex, :mime, "2.0.7", "b8d739037be7cd402aee1ba0306edfdef982687ee7e9859bee6198c1e7e2f128", [:mix], [], "hexpm", "6171188e399ee16023ffc5b76ce445eb6d9672e2e241d2df6050f3c771e80ccd"},
68-
"mimerl": {:hex, :mimerl, "1.3.0", "d0cd9fc04b9061f82490f6581e0128379830e78535e017f7780f37fea7545726", [:rebar3], [], "hexpm", "a1e15a50d1887217de95f0b9b0793e32853f7c258a5cd227650889b38839fe9d"},
68+
"mimerl": {:hex, :mimerl, "1.4.0", "3882a5ca67fbbe7117ba8947f27643557adec38fa2307490c4c4207624cb213b", [:rebar3], [], "hexpm", "13af15f9f68c65884ecca3a3891d50a7b57d82152792f3e19d88650aa126b144"},
6969
"mock": {:hex, :mock, "0.3.9", "10e44ad1f5962480c5c9b9fa779c6c63de9bd31997c8e04a853ec990a9d841af", [:mix], [{:meck, "~> 0.9.2", [hex: :meck, repo: "hexpm", optional: false]}], "hexpm", "9e1b244c4ca2551bb17bb8415eed89e40ee1308e0fbaed0a4fdfe3ec8a4adbd3"},
7070
"nimble_options": {:hex, :nimble_options, "1.1.1", "e3a492d54d85fc3fd7c5baf411d9d2852922f66e69476317787a7b2bb000a61b", [:mix], [], "hexpm", "821b2470ca9442c4b6984882fe9bb0389371b8ddec4d45a9504f00a66f650b44"},
7171
"nimble_ownership": {:hex, :nimble_ownership, "1.0.1", "f69fae0cdd451b1614364013544e66e4f5d25f36a2056a9698b793305c5aa3a6", [:mix], [], "hexpm", "3825e461025464f519f3f3e4a1f9b68c47dc151369611629ad08b636b73bb22d"},
@@ -98,7 +98,7 @@
9898
"telemetry_registry": {:hex, :telemetry_registry, "0.3.1", "14a3319a7d9027bdbff7ebcacf1a438f5f5c903057b93aee484cca26f05bdcba", [:mix, :rebar3], [{:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "6d0ca77b691cf854ed074b459a93b87f4c7f5512f8f7743c635ca83da81f939e"},
9999
"timex": {:hex, :timex, "3.7.11", "bb95cb4eb1d06e27346325de506bcc6c30f9c6dea40d1ebe390b262fad1862d1", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:gettext, "~> 0.20", [hex: :gettext, repo: "hexpm", optional: false]}, {:tzdata, "~> 1.1", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm", "8b9024f7efbabaf9bd7aa04f65cf8dcd7c9818ca5737677c7b76acbc6a94d1aa"},
100100
"tzdata": {:hex, :tzdata, "1.1.1", "20c8043476dfda8504952d00adac41c6eda23912278add38edc140ae0c5bcc46", [:mix], [{:hackney, "~> 1.17", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "a69cec8352eafcd2e198dea28a34113b60fdc6cb57eb5ad65c10292a6ba89787"},
101-
"unicode_util_compat": {:hex, :unicode_util_compat, "0.7.0", "bc84380c9ab48177092f43ac89e4dfa2c6d62b40b8bd132b1059ecc7232f9a78", [:rebar3], [], "hexpm", "25eee6d67df61960cf6a794239566599b09e17e668d3700247bc498638152521"},
101+
"unicode_util_compat": {:hex, :unicode_util_compat, "0.7.1", "a48703a25c170eedadca83b11e88985af08d35f37c6f664d6dcfb106a97782fc", [:rebar3], [], "hexpm", "b3a917854ce3ae233619744ad1e0102e05673136776fb2fa76234f3e03b23642"},
102102
"websock": {:hex, :websock, "0.5.3", "2f69a6ebe810328555b6fe5c831a851f485e303a7c8ce6c5f675abeb20ebdadc", [:mix], [], "hexpm", "6105453d7fac22c712ad66fab1d45abdf049868f253cf719b625151460b8b453"},
103103
"websock_adapter": {:hex, :websock_adapter, "0.5.8", "3b97dc94e407e2d1fc666b2fb9acf6be81a1798a2602294aac000260a7c4a47d", [:mix], [{:bandit, ">= 0.6.0", [hex: :bandit, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.6", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "315b9a1865552212b5f35140ad194e67ce31af45bcee443d4ecb96b5fd3f3782"},
104104
"xml_builder": {:hex, :xml_builder, "2.4.0", "b20d23077266c81f593360dc037ea398461dddb6638a329743da6c73afa56725", [:mix], [], "hexpm", "833e325bb997f032b5a1b740d2fd6feed3c18ca74627f9f5f30513a9ae1a232d"},
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
defmodule Cadet.Repo.Migrations.CreateTokenExchangeTable do
2+
use Ecto.Migration
3+
4+
def change do
5+
create table(:token_exchange) do
6+
add(:code, :string, null: false)
7+
add(:generated_at, :utc_datetime_usec, null: false)
8+
add(:expires_at, :utc_datetime_usec, null: false)
9+
add(:user_id, references(:users), null: false)
10+
timestamps()
11+
end
12+
end
13+
end

0 commit comments

Comments
 (0)