@@ -11,7 +11,6 @@ defmodule AlgoraWeb.Onboarding.OrgLive do
1111 alias AlgoraWeb.Components.Wordmarks
1212 alias AlgoraWeb.LocalStore
1313 alias Phoenix.LiveView.AsyncResult
14- alias Swoosh.Email
1514
1615 require Logger
1716
@@ -152,63 +151,6 @@ defmodule AlgoraWeb.Onboarding.OrgLive do
152151
153152 # === LIFECYCLE === #
154153
155- def mount (
156- _params ,
157- % {
158- "onboarding_email" => email ,
159- "onboarding_domain" => domain ,
160- "onboarding_tech_stack" => tech_stack ,
161- "onboarding_token" => login_code
162- } ,
163- socket
164- ) do
165- if Accounts . get_user_by_email ( email ) do
166- # user already exists, so onboarding is complete
167- # allow user to login with token until expiry
168- { :ok , redirect ( socket , to: AlgoraWeb.UserAuth . login_path ( email , login_code ) ) }
169- else
170- tech_stack_form =
171- % TechStackForm { }
172- |> TechStackForm . changeset ( % { tech_stack: String . split ( tech_stack , "," ) } )
173- |> to_form ( )
174-
175- email_form =
176- % EmailForm { }
177- |> EmailForm . changeset ( % { email: email , domain: domain } )
178- |> to_form ( )
179-
180- verificaiton_form =
181- % VerificationForm { }
182- |> VerificationForm . changeset ( % { code: login_code } )
183- |> to_form ( )
184-
185- case AlgoraWeb.UserAuth . verify_login_code ( login_code , email ) do
186- { :ok , _login_token } ->
187- { :ok ,
188- socket
189- |> assign ( :tech_stack_form , tech_stack_form )
190- |> assign ( :email_form , email_form )
191- |> assign ( :verification_form , verificaiton_form )
192- |> assign ( :preferences_form , PreferencesForm . init ( ) )
193- |> assign ( :step , :preferences )
194- |> assign ( :steps , @ steps )
195- |> assign ( :code_sent? , true )
196- |> assign ( :code_valid? , true )
197- |> assign ( :timezone , nil )
198- |> assign ( :user_metadata , AsyncResult . loading ( ) )
199- |> assign_matching_devs ( )
200- |> start_async ( :fetch_metadata , fn -> Algora.Crawler . fetch_user_metadata ( email ) end )
201- |> assign ( :user_metadata , AsyncResult . loading ( ) ) }
202-
203- { :error , _invalid } ->
204- { :ok ,
205- socket
206- |> put_flash ( :error , "Invalid auth token" )
207- |> redirect ( to: "/" ) }
208- end
209- end
210- end
211-
212154 def mount ( _params , _session , socket ) do
213155 { :ok ,
214156 socket
@@ -221,6 +163,7 @@ defmodule AlgoraWeb.Onboarding.OrgLive do
221163 |> assign ( :code_sent? , false )
222164 |> assign ( :code_valid? , nil )
223165 |> assign ( :timezone , nil )
166+ |> assign ( :secret , nil )
224167 |> assign ( :user_metadata , AsyncResult . loading ( ) )
225168 |> assign_matching_devs ( ) }
226169 end
@@ -284,22 +227,13 @@ defmodule AlgoraWeb.Onboarding.OrgLive do
284227 case changeset do
285228 % { valid?: true } = changeset ->
286229 email = get_field ( changeset , :email )
287- domain = get_field ( changeset , :domain )
288- tech_stack = get_field ( socket . assigns . tech_stack_form . source , :tech_stack )
289- login_code = AlgoraWeb.UserAuth . generate_login_code ( email , domain , tech_stack )
290-
291- name = email |> String . split ( "@" ) |> List . first ( ) |> String . capitalize ( )
230+ { secret , code } = AlgoraWeb.UserAuth . generate_totp ( )
292231
293- { :ok , _ } =
294- Email . new ( )
295- |> Email . to ( { name , email } )
296- |> Email . from ( { "Algora" , "[email protected] " } ) 297- |> Email . subject ( "Algora sign-in verification code" )
298- |> Email . text_body ( AlgoraWeb.UserAuth . login_email ( email , name , login_code ) )
299- |> Algora.Mailer . deliver ( )
232+ { :ok , _ } = Accounts . deliver_totp_signup_email ( email , code )
300233
301234 { :noreply ,
302235 socket
236+ |> LocalStore . assign_cached ( :secret , secret )
303237 |> LocalStore . assign_cached ( :email_form , to_form ( changeset ) )
304238 |> LocalStore . assign_cached ( :code_sent? , true )
305239 |> assign_matching_devs ( )
@@ -323,102 +257,110 @@ defmodule AlgoraWeb.Onboarding.OrgLive do
323257 email = get_field ( socket . assigns . email_form . source , :email )
324258 domain = get_field ( socket . assigns . email_form . source , :domain )
325259 tech_stack = get_field ( socket . assigns . tech_stack_form . source , :tech_stack )
326- login_code = get_field ( socket . assigns . verification_form . source , :code )
327260 preferences = changeset . changes
328261
329- metadata =
330- case socket . assigns . user_metadata do
331- % AsyncResult { ok?: true , result: metadata } -> metadata
332- _ -> % { }
333- end
334-
335- org_name =
336- case get_in ( metadata , [ :org , :display_name ] ) do
337- nil ->
338- domain
339- |> String . split ( "." )
340- |> List . first ( )
341- |> String . capitalize ( )
342-
343- name ->
344- name
345- end
346-
347- org_handle =
348- case get_in ( metadata , [ :org , :handle ] ) do
349- nil ->
350- domain
351- |> String . split ( "." )
352- |> List . first ( )
353- |> String . downcase ( )
354-
355- handle ->
356- handle
357- end
358-
359- user_handle = Organizations . generate_handle_from_email ( email )
360-
361- org_params =
362- % {
363- display_name: org_name ,
364- bio:
365- get_in ( metadata , [ :org , :bio ] ) ||
366- get_in ( metadata , [ :org , :og_description ] ) ||
367- get_in ( metadata , [ :org , :og_title ] ) ,
368- avatar_url: get_in ( metadata , [ :org , :avatar_url ] ) || get_in ( metadata , [ :org , :favicon_url ] ) ,
369- handle: org_handle ,
370- domain: domain ,
371- og_title: get_in ( metadata , [ :org , :og_title ] ) ,
372- og_image_url: get_in ( metadata , [ :org , :og_image_url ] ) ,
373- tech_stack: tech_stack ,
374- hiring: get_in ( preferences , [ :hiring ] ) ,
375- categories: get_in ( preferences , [ :categories ] ) ,
376- website_url: get_in ( metadata , [ :org , :website_url ] ) ,
377- twitter_url: get_in ( metadata , [ :org , :socials , :twitter ] ) ,
378- github_url: get_in ( metadata , [ :org , :socials , :github ] ) ,
379- youtube_url: get_in ( metadata , [ :org , :socials , :youtube ] ) ,
380- twitch_url: get_in ( metadata , [ :org , :socials , :twitch ] ) ,
381- discord_url: get_in ( metadata , [ :org , :socials , :discord ] ) ,
382- slack_url: get_in ( metadata , [ :org , :socials , :slack ] ) ,
383- linkedin_url: get_in ( metadata , [ :org , :socials , :linkedin ] )
384- }
385-
386- user_params =
387- % {
388- email: email ,
389- display_name: user_handle ,
390- avatar_url: get_in ( metadata , [ :avatar_url ] ) ,
391- handle: user_handle ,
392- tech_stack: tech_stack ,
393- timezone: socket . assigns . timezone
394- }
395-
396- member_params =
397- % {
398- role: :admin
399- }
400-
401- params =
402- % {
403- organization: org_params ,
404- user: user_params ,
405- member: member_params
406- }
407-
408- socket =
409- case Algora.Organizations . onboard_organization ( params ) do
410- { :ok , _ } ->
411- redirect ( socket , to: AlgoraWeb.UserAuth . login_path ( email , login_code ) )
412-
413- { :error , name , changeset , _created } ->
414- Logger . error ( "error onboarding organization: #{ inspect ( name ) } #{ inspect ( changeset ) } " )
415-
416- socket
417- |> put_flash ( :error , "Something went wrong. Please try again." )
418- |> redirect ( to: "/" )
419- end
420-
421- { :noreply , socket }
262+ if socket . assigns . code_valid? do
263+ metadata =
264+ case socket . assigns . user_metadata do
265+ % AsyncResult { ok?: true , result: metadata } -> metadata
266+ _ -> % { }
267+ end
268+
269+ org_name =
270+ case get_in ( metadata , [ :org , :display_name ] ) do
271+ nil ->
272+ domain
273+ |> String . split ( "." )
274+ |> List . first ( )
275+ |> String . capitalize ( )
276+
277+ name ->
278+ name
279+ end
280+
281+ org_handle =
282+ case get_in ( metadata , [ :org , :handle ] ) do
283+ nil ->
284+ domain
285+ |> String . split ( "." )
286+ |> List . first ( )
287+ |> String . downcase ( )
288+
289+ handle ->
290+ handle
291+ end
292+
293+ user_handle = Organizations . generate_handle_from_email ( email )
294+
295+ org_params =
296+ % {
297+ display_name: org_name ,
298+ bio:
299+ get_in ( metadata , [ :org , :bio ] ) ||
300+ get_in ( metadata , [ :org , :og_description ] ) ||
301+ get_in ( metadata , [ :org , :og_title ] ) ,
302+ avatar_url: get_in ( metadata , [ :org , :avatar_url ] ) || get_in ( metadata , [ :org , :favicon_url ] ) ,
303+ handle: org_handle ,
304+ domain: domain ,
305+ og_title: get_in ( metadata , [ :org , :og_title ] ) ,
306+ og_image_url: get_in ( metadata , [ :org , :og_image_url ] ) ,
307+ tech_stack: tech_stack ,
308+ hiring: get_in ( preferences , [ :hiring ] ) ,
309+ categories: get_in ( preferences , [ :categories ] ) ,
310+ website_url: get_in ( metadata , [ :org , :website_url ] ) ,
311+ twitter_url: get_in ( metadata , [ :org , :socials , :twitter ] ) ,
312+ github_url: get_in ( metadata , [ :org , :socials , :github ] ) ,
313+ youtube_url: get_in ( metadata , [ :org , :socials , :youtube ] ) ,
314+ twitch_url: get_in ( metadata , [ :org , :socials , :twitch ] ) ,
315+ discord_url: get_in ( metadata , [ :org , :socials , :discord ] ) ,
316+ slack_url: get_in ( metadata , [ :org , :socials , :slack ] ) ,
317+ linkedin_url: get_in ( metadata , [ :org , :socials , :linkedin ] )
318+ }
319+
320+ user_params =
321+ % {
322+ email: email ,
323+ display_name: user_handle ,
324+ avatar_url: get_in ( metadata , [ :avatar_url ] ) ,
325+ handle: user_handle ,
326+ tech_stack: tech_stack ,
327+ timezone: socket . assigns . timezone
328+ }
329+
330+ member_params =
331+ % {
332+ role: :admin
333+ }
334+
335+ params =
336+ % {
337+ organization: org_params ,
338+ user: user_params ,
339+ member: member_params
340+ }
341+
342+ socket =
343+ case Algora.Organizations . onboard_organization ( params ) do
344+ { :ok , _ } ->
345+ redirect ( socket , to: AlgoraWeb.UserAuth . generate_login_path ( email ) )
346+
347+ { :error , name , changeset , _created } ->
348+ Logger . error ( "error onboarding organization: #{ inspect ( name ) } #{ inspect ( changeset ) } " )
349+
350+ socket
351+ |> put_flash ( :error , "Something went wrong. Please try again." )
352+ |> redirect ( to: "/" )
353+ end
354+
355+ { :noreply , socket }
356+ else
357+ throttle ( )
358+
359+ { :noreply ,
360+ socket
361+ |> put_flash ( :error , "Invalid verification code" )
362+ |> LocalStore . assign_cached ( :step , :email ) }
363+ end
422364
423365 % { valid?: false } ->
424366 { :noreply , LocalStore . assign_cached ( socket , :preferences_form , to_form ( changeset ) ) }
@@ -434,20 +376,20 @@ defmodule AlgoraWeb.Onboarding.OrgLive do
434376 case changeset do
435377 % { valid?: true } = changeset ->
436378 code = get_field ( changeset , :code )
437- email = get_field ( socket . assigns . email_form . source , :email )
438379
439- case AlgoraWeb.UserAuth . verify_login_code ( code , email ) do
440- { :ok , _login_token } ->
441- { :noreply ,
442- socket
443- |> LocalStore . assign_cached ( :verification_form , to_form ( changeset ) )
444- |> LocalStore . assign_cached ( :step , :preferences ) }
445-
446- { :error , _reason } ->
447- { :noreply ,
448- socket
449- |> LocalStore . assign_cached ( :verification_form , to_form ( changeset ) )
450- |> LocalStore . assign_cached ( :code_valid? , false ) }
380+ if AlgoraWeb.UserAuth . valid_totp? ( socket . assigns . secret , String . trim ( code ) ) do
381+ { :noreply ,
382+ socket
383+ |> LocalStore . assign_cached ( :verification_form , to_form ( changeset ) )
384+ |> LocalStore . assign_cached ( :code_valid? , true )
385+ |> LocalStore . assign_cached ( :step , :preferences ) }
386+ else
387+ throttle ( )
388+
389+ { :noreply ,
390+ socket
391+ |> LocalStore . assign_cached ( :verification_form , to_form ( changeset ) )
392+ |> LocalStore . assign_cached ( :code_valid? , false ) }
451393 end
452394
453395 % { valid?: false } = changeset ->
@@ -864,4 +806,6 @@ defmodule AlgoraWeb.Onboarding.OrgLive do
864806 def handle_async ( :fetch_metadata , { :exit , reason } , socket ) do
865807 { :noreply , assign ( socket , :user_metadata , AsyncResult . failed ( socket . assigns . user_metadata , reason ) ) }
866808 end
809+
810+ defp throttle , do: :timer . sleep ( 1000 )
867811end
0 commit comments