@@ -2,20 +2,56 @@ defmodule AlgoraWeb.SignInLive do
2
2
@ moduledoc false
3
3
use AlgoraWeb , :live_view
4
4
5
+ alias Algora.Accounts.User
6
+ alias Swoosh.Email
7
+
5
8
def render ( assigns ) do
6
9
~H"""
7
- < div class = "min-h-[calc(100vh-64px)] flex flex-col justify-center " >
8
- < div class = "mb-[64px] mx-auto max-w-3xl p-12 sm:mx-auto sm:w-full sm:max-w-sm sm:p-24 " >
9
- < h2 class = "text-center text-3xl font-extrabold text-gray-50 " >
10
- Algora Console
11
- </ h2 >
12
- < . link
13
- href = { @ authorize_url }
14
- rel = "noopener "
15
- class = "mt-8 flex w-full justify-center rounded-md border border-transparent bg-indigo-600 px-4 py-2 text-sm font-medium text-white shadow-sm hover:bg-indigo-600 focus:outline-none focus:ring-2 focus:ring-indigo-400 focus:ring-offset-2 "
16
- >
17
- Sign in with GitHub
18
- </ . link >
10
+ < div class = "mx-auto max-w-sm " >
11
+ < div class = "min-h-[calc(100vh-64px)] flex flex-col justify-center " >
12
+ < div class = "mb mx-auto max-w-3xl p-4 sm:mx-auto sm:w-full sm:max-w-sm " >
13
+ < h2 class = "text-center text-3xl font-extrabold text-gray-50 p-4 " >
14
+ Algora Console
15
+ </ h2 >
16
+ < . header class = "text-center p-4 " >
17
+ Sign in with Github
18
+ <: subtitle > Sign in and link your Github Account</: subtitle >
19
+ </ . header >
20
+ < . link
21
+ href = { @ authorize_url }
22
+ rel = "noopener "
23
+ class = "mt-4 flex w-full justify-center rounded-md border border-transparent bg-indigo-600 px-4 py-2 text-sm font-medium text-white shadow-sm hover:bg-indigo-600 focus:outline-none focus:ring-2 focus:ring-indigo-400 focus:ring-offset-2 "
24
+ >
25
+ Authorize with GitHub
26
+ </ . link >
27
+ </ div >
28
+ < div class = "border-t border-gray-800 my-4 " > </ div >
29
+ < . header class = "text-center p-4 " >
30
+ Sign in with Email
31
+ <: subtitle :if = { ! @ secret_code } > We'll send a login code to your inbox</: subtitle >
32
+ <: subtitle :if = { @ secret_code } > We sent a login code to your inbox</: subtitle >
33
+ </ . header >
34
+
35
+ < . simple_form for = { @ form } id = "send_login_code_form " phx-submit = "send_login_code " >
36
+ < . input :if = { ! @ secret_code } field = { @ form [ :email ] } type = "email " placeholder = "Email " required />
37
+ < . input
38
+ :if = { @ secret_code }
39
+ field = { @ form [ :login_code ] }
40
+ type = "text "
41
+ placeholder = "Enter Login Code "
42
+ required
43
+ />
44
+ <: actions >
45
+ < . button
46
+ if = { ! @ secret_code }
47
+ phx-disable-with = "Sending... "
48
+ class = "mt-4 flex w-full justify-center rounded-md border border-transparent bg-indigo-600 px-4 py-2 text-sm font-medium text-white shadow-sm hover:bg-indigo-600 focus:outline-none focus:ring-2 focus:ring-indigo-400 focus:ring-offset-2 "
49
+ >
50
+ < span :if = { ! @ secret_code } > Send login code</ span >
51
+ < div :if = { @ secret_code } > Login with code</ div >
52
+ </ . button >
53
+ </: actions >
54
+ </ . simple_form >
19
55
</ div >
20
56
</ div >
21
57
"""
@@ -28,6 +64,86 @@ defmodule AlgoraWeb.SignInLive do
28
64
return_to -> Algora.Github . authorize_url ( % { return_to: return_to } )
29
65
end
30
66
31
- { :ok , assign ( socket , authorize_url: authorize_url ) }
67
+ changeset = User . login_changeset ( % User { } , % { } )
68
+
69
+ { :ok ,
70
+ socket
71
+ |> assign ( authorize_url: authorize_url )
72
+ |> assign ( secret_code: nil )
73
+ |> assign_form ( changeset ) }
74
+ end
75
+
76
+ def handle_event ( "send_login_code" , % { "user" => % { "email" => email } } , socket ) do
77
+ code = Nanoid . generate ( )
78
+
79
+ case Algora.Accounts . get_user_by_email ( email ) do
80
+ % User { } = user ->
81
+ changeset = User . login_changeset ( % User { } , % { } )
82
+ case send_login_code_to_user! ( user , code ) do
83
+ { :ok , _id } ->
84
+ { :noreply ,
85
+ socket
86
+ |> assign ( :secret_code , code )
87
+ |> assign ( :user , user )
88
+ |> assign_form ( changeset )
89
+ |> put_flash ( :info , "Login code sent to #{ email } !" ) }
90
+
91
+ { :error , _reason } ->
92
+ # capture_error reason
93
+ { :noreply , put_flash ( socket , :error , "We had trouble sending mail to #{ email } . Please try again" ) }
94
+ end
95
+
96
+ nil ->
97
+ throttle ( )
98
+ { :noreply , put_flash ( socket , :error , "Email address not found." ) }
99
+ end
100
+ end
101
+
102
+ def handle_event ( "send_login_code" , % { "user" => % { "login_code" => code } } , socket ) do
103
+ if Plug.Crypto . secure_compare ( code , socket . assigns . secret_code ) do
104
+ user = socket . assigns . user
105
+ token = AlgoraWeb.UserAuth . generate_login_code ( user . email )
106
+ path = AlgoraWeb.UserAuth . login_path ( user . email , token )
107
+
108
+ { :noreply ,
109
+ socket
110
+ |> redirect ( to: path )
111
+ |> put_flash ( :info , "Logged in successfully!" ) }
112
+ else
113
+ throttle ( )
114
+ { :noreply , put_flash ( socket , :info , "Invalid login code" ) }
115
+ end
32
116
end
117
+
118
+ defp assign_form ( socket , % Ecto.Changeset { } = changeset ) do
119
+ assign ( socket , :form , to_form ( changeset ) )
120
+ end
121
+
122
+ @ from_name "Algora"
123
+
124
+
125
+ defp send_login_code_to_user! ( user , code ) do
126
+ email =
127
+ Email . new ( )
128
+ |> Email . to ( { user . display_name , user . email } )
129
+ |> Email . from ( { @ from_name , @ from_email } )
130
+ |> Email . subject ( "Login code for Algora" )
131
+ |> Email . text_body ( """
132
+ Here is your login code for Algora!
133
+
134
+ #{ code }
135
+
136
+ If you didn't request this link, you can safely ignore this email.
137
+
138
+ --------------------------------------------------------------------------------
139
+
140
+ For correspondence, please email the Algora founders at [email protected] and [email protected]
141
+
142
+ © 2025 Algora PBC.
143
+ """ )
144
+
145
+ Algora.Mailer . deliver ( email )
146
+ end
147
+
148
+ defp throttle , do: :timer . sleep ( 1000 )
33
149
end
0 commit comments