mas_handlers/views/register/steps/
registration_token.rs1use anyhow::Context as _;
7use axum::{
8 Form,
9 extract::{Path, State},
10 response::{Html, IntoResponse, Response},
11};
12use mas_axum_utils::{
13 InternalError,
14 cookies::CookieJar,
15 csrf::{CsrfExt as _, ProtectedForm},
16};
17use mas_router::{PostAuthAction, UrlBuilder};
18use mas_storage::{BoxClock, BoxRepository, BoxRng};
19use mas_templates::{
20 FieldError, RegisterStepsRegistrationTokenContext, RegisterStepsRegistrationTokenFormField,
21 TemplateContext as _, Templates, ToFormState,
22};
23use serde::{Deserialize, Serialize};
24use ulid::Ulid;
25
26use crate::{PreferredLanguage, views::shared::OptionalPostAuthAction};
27
28#[derive(Deserialize, Serialize)]
29pub(crate) struct RegistrationTokenForm {
30 #[serde(default)]
31 token: String,
32}
33
34impl ToFormState for RegistrationTokenForm {
35 type Field = mas_templates::RegisterStepsRegistrationTokenFormField;
36}
37
38#[tracing::instrument(
39 name = "handlers.views.register.steps.registration_token.get",
40 fields(user_registration.id = %id),
41 skip_all,
42)]
43pub(crate) async fn get(
44 mut rng: BoxRng,
45 clock: BoxClock,
46 PreferredLanguage(locale): PreferredLanguage,
47 State(templates): State<Templates>,
48 State(url_builder): State<UrlBuilder>,
49 mut repo: BoxRepository,
50 Path(id): Path<Ulid>,
51 cookie_jar: CookieJar,
52) -> Result<Response, InternalError> {
53 let (csrf_token, cookie_jar) = cookie_jar.csrf_token(&clock, &mut rng);
54
55 let registration = repo
56 .user_registration()
57 .lookup(id)
58 .await?
59 .context("Could not find user registration")
60 .map_err(InternalError::from_anyhow)?;
61
62 if registration.completed_at.is_some() {
64 let post_auth_action: Option<PostAuthAction> = registration
65 .post_auth_action
66 .map(serde_json::from_value)
67 .transpose()?;
68
69 return Ok((
70 cookie_jar,
71 OptionalPostAuthAction::from(post_auth_action)
72 .go_next(&url_builder)
73 .into_response(),
74 )
75 .into_response());
76 }
77
78 if registration.user_registration_token_id.is_some() {
80 let destination = mas_router::RegisterDisplayName::new(registration.id);
81 return Ok((cookie_jar, url_builder.redirect(&destination)).into_response());
82 }
83
84 let ctx = RegisterStepsRegistrationTokenContext::new()
85 .with_csrf(csrf_token.form_value())
86 .with_language(locale);
87
88 let content = templates.render_register_steps_registration_token(&ctx)?;
89
90 Ok((cookie_jar, Html(content)).into_response())
91}
92
93#[tracing::instrument(
94 name = "handlers.views.register.steps.registration_token.post",
95 fields(user_registration.id = %id),
96 skip_all,
97)]
98pub(crate) async fn post(
99 mut rng: BoxRng,
100 clock: BoxClock,
101 PreferredLanguage(locale): PreferredLanguage,
102 State(templates): State<Templates>,
103 State(url_builder): State<UrlBuilder>,
104 mut repo: BoxRepository,
105 Path(id): Path<Ulid>,
106 cookie_jar: CookieJar,
107 Form(form): Form<ProtectedForm<RegistrationTokenForm>>,
108) -> Result<Response, InternalError> {
109 let registration = repo
110 .user_registration()
111 .lookup(id)
112 .await?
113 .context("Could not find user registration")
114 .map_err(InternalError::from_anyhow)?;
115
116 if registration.completed_at.is_some() {
118 let post_auth_action: Option<PostAuthAction> = registration
119 .post_auth_action
120 .map(serde_json::from_value)
121 .transpose()?;
122
123 return Ok((
124 cookie_jar,
125 OptionalPostAuthAction::from(post_auth_action)
126 .go_next(&url_builder)
127 .into_response(),
128 )
129 .into_response());
130 }
131
132 let form = cookie_jar.verify_form(&clock, form)?;
133
134 let (csrf_token, cookie_jar) = cookie_jar.csrf_token(&clock, &mut rng);
135
136 let token = form.token.trim();
138 if token.is_empty() {
139 let ctx = RegisterStepsRegistrationTokenContext::new()
140 .with_form_state(form.to_form_state().with_error_on_field(
141 RegisterStepsRegistrationTokenFormField::Token,
142 FieldError::Required,
143 ))
144 .with_csrf(csrf_token.form_value())
145 .with_language(locale);
146
147 return Ok((
148 cookie_jar,
149 Html(templates.render_register_steps_registration_token(&ctx)?),
150 )
151 .into_response());
152 }
153
154 let Some(registration_token) = repo.user_registration_token().find_by_token(token).await?
156 else {
157 let ctx = RegisterStepsRegistrationTokenContext::new()
158 .with_form_state(form.to_form_state().with_error_on_field(
159 RegisterStepsRegistrationTokenFormField::Token,
160 FieldError::Invalid,
161 ))
162 .with_csrf(csrf_token.form_value())
163 .with_language(locale);
164
165 return Ok((
166 cookie_jar,
167 Html(templates.render_register_steps_registration_token(&ctx)?),
168 )
169 .into_response());
170 };
171
172 if !registration_token.is_valid(clock.now()) {
174 tracing::warn!("Registration token isn't valid (expired or already used)");
175 let ctx = RegisterStepsRegistrationTokenContext::new()
176 .with_form_state(form.to_form_state().with_error_on_field(
177 RegisterStepsRegistrationTokenFormField::Token,
178 FieldError::Invalid,
179 ))
180 .with_csrf(csrf_token.form_value())
181 .with_language(locale);
182
183 return Ok((
184 cookie_jar,
185 Html(templates.render_register_steps_registration_token(&ctx)?),
186 )
187 .into_response());
188 }
189
190 let registration = repo
192 .user_registration()
193 .set_registration_token(registration, ®istration_token)
194 .await?;
195
196 repo.save().await?;
197
198 let destination = mas_router::RegisterFinish::new(registration.id);
200 Ok((cookie_jar, url_builder.redirect(&destination)).into_response())
201}