subgraph/graphql/schema/create_auth_service/start_register/
mod.rs

1use async_graphql::{
2    dynamic::{Field, FieldFuture, InputValue, TypeRef},
3    Value,
4};
5use bson::{doc, Bson};
6use log::{debug, error, trace};
7
8use crate::{
9    configuration::subgraph::data_sources::sql::DialectEnum,
10    data_sources::{sql::PoolEnum, DataSource, DataSources},
11    graphql::schema::ServiceSchema,
12};
13
14impl ServiceSchema {
15    pub fn create_register_start(mut self) -> Self {
16        debug!("Creating register start");
17        let auth_config = match self.subgraph_config.service.auth.clone() {
18            Some(auth) => auth,
19            None => {
20                panic!("Auth config not found.");
21            }
22        };
23
24        let resolver = Field::new(
25            "register_start",
26            TypeRef::named_nn(TypeRef::STRING),
27            move |ctx| {
28                debug!("Resolving register start");
29                let auth_config = auth_config.clone();
30
31                FieldFuture::new(async move {
32                    let identifier = match ctx.args.try_get("identifier") {
33                        Ok(input) => input
34                            .deserialize::<String>().map_err(|e| {
35                                error!("Failed to get input: {:?}", e);
36                                async_graphql::Error::new(format!("Failed to get input: {:?}", e))
37                            })?,
38                        Err(e) => {
39                            return Err(async_graphql::Error::new(format!(
40                                "Failed to get input: {:?}",
41                                e
42                            )))
43                        }
44                    };
45
46                    //match name of data source to the auth_config.data_source string
47                    let data_sources = ctx.data_unchecked::<DataSources>();
48                    let data_source = DataSources::get_data_source_by_name(
49                        &data_sources,
50                        &auth_config.data_source,
51                    );
52
53                    // Check if user exists. If previous register, reject, else delete the user.
54                    let user = ServiceSchema::get_user(&data_source, &identifier).await; 
55
56                    if !user.is_err() && user.clone().unwrap().clone().is_some() {
57                        if user.clone().unwrap().unwrap().passkey.is_some() {
58                            error!("User already exists: {:?}", &identifier);
59                            return Err(async_graphql::Error::new(format!(
60                                "User already exists: {:?}",
61                                &identifier
62                            )));
63                        }else {
64                            ServiceSchema::delete_user(&data_source, &identifier).await?;
65                        }
66                    }
67
68                    trace!("Creating webauthn service");
69
70                    let webauthn = ServiceSchema::build_webauthn(&auth_config).map_err(|e| {
71                        error!("Failed to build webauthn: {:?}", e);
72                        async_graphql::Error::new(format!("Failed to build webauthn: {:?}", e))
73                    })?;
74
75                    let user_uuid = uuid::Uuid::new_v4();
76
77                    let (ccr, reg_state) = webauthn.start_passkey_registration(
78                        user_uuid.clone(),
79                        &identifier,
80                        &identifier,
81                        None,
82                    ).map_err(|e| {
83                        error!("Failed to start passkey registration: {:?}", e);
84                        async_graphql::Error::new(format!(
85                            "Failed to start passkey registration: {:?}",
86                            e
87                        ))
88                    })?;
89
90                    let reg_state = match serde_json::to_string(&reg_state) {
91                        Ok(reg_state) => reg_state,
92                        Err(e) => {
93                            return Err(async_graphql::Error::new(format!(
94                                "Failed to serialize registration state: {}",
95                                e
96                            )))
97                        }
98                    };
99
100                    let user_uuid_string = user_uuid.to_string();
101
102                    // Save registration state to database
103                    match &data_source {
104                        DataSource::Mongo(mongo_ds) => {
105                            let user = doc! {
106                                "uuid": user_uuid_string.to_string(),
107                                "identifier": identifier.clone(),
108                                "registration_state": &reg_state,
109                                "authentication_state": Bson::Null,
110                                "passkey": Bson::Null,
111                            };
112
113                            mongo_ds
114                                .db
115                                .collection("subgraph_user")
116                                .insert_one(user, None)
117                                .await?;
118                        }
119                        DataSource::SQL(sql_ds) => {
120                            match sql_ds.config.dialect {
121                                DialectEnum::MYSQL => {
122                                    let query = sqlx::query("INSERT INTO subgraph_user (uuid, identifier, registration_state) VALUES (?, ?, ?);")
123                                        .bind(&user_uuid_string)
124                                        .bind(&identifier)
125                                        .bind(&reg_state);
126                                    match sql_ds.pool.clone() {
127                                        PoolEnum::MySql(pool) => {
128                                            query.execute(&pool).await?;
129                                        }
130                                        _ => unreachable!(),
131                                    };
132                                }
133                                DialectEnum::SQLITE => {
134                                    let query = sqlx::query("INSERT INTO subgraph_user (uuid, identifier, registration_state) VALUES (?, ?, ?);")
135                                        .bind(&user_uuid_string)
136                                        .bind(&identifier)
137                                        .bind(&reg_state);
138                                    match sql_ds.pool.clone() {
139                                        PoolEnum::SqLite(pool) => {
140                                            query.execute(&pool).await?;
141                                        }
142                                        _ => unreachable!(),
143                                    };
144                                }
145                                DialectEnum::POSTGRES => {
146                                    let query = sqlx::query("INSERT INTO subgraph_user (uuid, identifier, registration_state) VALUES ($1, $2, $3);")
147                                        .bind(&user_uuid)
148                                        .bind(&identifier)
149                                        .bind(&reg_state);
150                                    match sql_ds.pool.clone() {
151                                        PoolEnum::Postgres(pool) => {
152                                            query.execute(&pool).await?;
153                                        }
154                                        _ => unreachable!(),
155                                    };
156                                }
157                            };
158                        }
159                        _ => panic!("Data Source not supported."),
160                    };
161
162                    trace!("Challenge created: {:?}", ccr);
163                    trace!("Registration state created: {:?}", reg_state);
164
165                    let json = match serde_json::to_value(&ccr) {
166                        Ok(json) => json,
167                        Err(e) => {
168                            return Err(async_graphql::Error::new(format!(
169                                "Failed to serialize challenge: {}",
170                                e
171                            )))
172                        }
173                    };
174
175                    let value = Value::from_json(json);
176
177                    match value {
178                        Ok(value) => Ok(Some(value)),
179                        Err(_) => Err(async_graphql::Error::new("Failed to resolve challenge.")),
180                    }
181                })
182            },
183        )
184        .argument(InputValue::new(
185            "identifier",
186            TypeRef::named_nn(TypeRef::STRING),
187        ));
188
189        self.mutation = self.mutation.field(resolver);
190        self
191    }
192}