fars 0.2.0

An unofficial Rust client for the Firebase Auth REST API.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
# fars

An unofficial Rust client for the [Firebase Auth REST API](https://firebase.google.com/docs/reference/rest/auth).

## Installation

Please install this library by adding by CLI:

```shell
$ cargo add fars
```

or adding dependency to your `Cargo.toml`:

```toml
[dependencies]
fars = "0.2.0"
```

## Features
All features in this crate are as follows:

- default
    - [Session-based interfaces]#api-usages
    - [Raw API interfaces]#optional-raw-api-interfaces
- (Optional) `verify`
    - [ID token verification]#optional-id-token-verification
- (Optional) `custom_client`
    - [HTTP client customization]#http-client-customization 

## Supported APIs

Suppoted APIs of the [Firebase Auth REST API](https://firebase.google.com/docs/reference/rest/auth) are as follows:

- [x] [Exchange custom token for an ID and refresh token](https://firebase.google.com/docs/reference/rest/auth#section-verify-custom-token)
- [x] [Exchange a refresh token for an ID token](https://firebase.google.com/docs/reference/rest/auth#section-refresh-token)
- [x] [Sign up with email / password](https://firebase.google.com/docs/reference/rest/auth#section-create-email-password)
- [x] [Sign in with email / password](https://firebase.google.com/docs/reference/rest/auth#section-sign-in-email-password)
- [x] [Sign in anonymously](https://firebase.google.com/docs/reference/rest/auth#section-sign-in-anonymously)
- [x] [Sign in with OAuth credential](https://firebase.google.com/docs/reference/rest/auth#section-sign-in-with-oauth-credential)
- [x] [Fetch providers for email](https://firebase.google.com/docs/reference/rest/auth#section-fetch-providers-for-email)
- [x] [Send password reset email](https://firebase.google.com/docs/reference/rest/auth#section-send-password-reset-email)
- [ ] Not tested [Verify password reset code]https://firebase.google.com/docs/reference/rest/auth#section-verify-password-reset-code
- [ ] Not tested [Confirm password reset]https://firebase.google.com/docs/reference/rest/auth#section-confirm-reset-password
- [x] [Change email](https://firebase.google.com/docs/reference/rest/auth#section-change-email)
- [x] [Change password](https://firebase.google.com/docs/reference/rest/auth#section-change-password)
- [x] [Update profile](https://firebase.google.com/docs/reference/rest/auth#section-update-profile)
- [x] [Get user data](https://firebase.google.com/docs/reference/rest/auth#section-get-account-info)
- [x] [Link with email/password](https://firebase.google.com/docs/reference/rest/auth#section-link-with-email-password)
- [x] [Link with OAuth credential](https://firebase.google.com/docs/reference/rest/auth#section-link-with-oauth-credential)
- [x] [Unlink provider](https://firebase.google.com/docs/reference/rest/auth#section-unlink-provider)
- [x] [Send email verification](https://firebase.google.com/docs/reference/rest/auth#section-send-email-verification)
- [ ] Not tested [Confirm email verification]https://firebase.google.com/docs/reference/rest/auth#section-confirm-email-verification
- [x] [Delete account](https://firebase.google.com/docs/reference/rest/auth#section-delete-account)

> [!NOTE]
> Unsupported APIs have already been implemented but not tested.

## Supported OAuth ID providers

Supported OAuth ID provides are as follows:

- [ ] Not implemented Apple (`apple.com`)
- [ ] Not implemented Apple Game Center (`gc.apple.com`)
- [ ] Not tested Facebook (`facebook.com`)
- [ ] Not implemented GitHub (`github.com`)
- [x] Google (`google.com`)
- [ ] Not implemented Google Play Games (`playgames.google.com`)
- [ ] Not implemented LinkedIn (`linkedin.com`)
- [ ] Not implemented Microsoft (`microsoft.com`)
- [ ] Not tested Twitter (`twitter.com`)
- [ ] Not implemented Yahoo (`yahoo.com`)

> [!NOTE]
> Unsupported providers have either not been tested or the format of `IdpPostBody` is not documented at the [official API reference]https://firebase.google.com/docs/reference/rest/auth.

## API Usages

Provides semantic interfaces based on a session (`fars::Session`) as following steps.

> [!IMPORTANT]
> - ID token (`fars::Session.id_token`) has expiration date.
> - API calling through a session automatically refresh an ID token by the [refresh token API]https://firebase.google.com/docs/reference/rest/auth#section-refresh-token when the ID token has been expired.
> - All APIs through session cosume session and return new session that has same ID token or refreshed one except for the [delete account API]https://firebase.google.com/docs/reference/rest/auth#section-delete-account.
> 
> Therefore you have to **update** session every time you use APIs through a session by returned new session.

### A usage for a siging in user

1. Create a config (`fars::Config`) with your Firebase project API key.
2. Sign in or sign up by supported options (Email & password / OAuth / Anonymous / Stored refresh token) through the config then get the session (`fars::Session`) for the siging in user.
3. Use Auth APIs for the siging in user through the session, or use ID token (`fars::Session.id_token`) for other Firebase APIs.

A sample code to [sign up with email / password](https://firebase.google.com/docs/reference/rest/auth#section-create-email-password) and to [get user data](https://firebase.google.com/docs/reference/rest/auth#section-get-account-info) with [tokio](https://github.com/tokio-rs/tokio) and [anyhow](https://github.com/dtolnay/anyhow) is as follows:

```rust
use fars::Config;
use fars::ApiKey;
use fars::Email;
use fars::Password;

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    // 1. Create a config with your Firebase project API key.
    let config = Config::new(
        ApiKey::new("your-firebase-project-api-key"),
    );
    
    // 2. Sign up with email and password then get a session.
    let session = config.sign_up_with_email_password(
        Email::new("user@example.com"),
        Password::new("password"),
    ).await?;

    // 3. Get user data through the session and get a new session.
    let (new_session, user_data) = session.get_user_data().await?;

    // 4. Do something with new_session and user_data.

    Ok(())
}
```

### A usage for not siging in user

1. Create a config (`fars::Config`) with your Firebase project API key.
2. Use Auth APIs for a not siging in user through the config.

A sample code to [send password reset email](https://firebase.google.com/docs/reference/rest/auth#section-send-password-reset-email) with [tokio](https://github.com/tokio-rs/tokio) and [anyhow](https://github.com/dtolnay/anyhow) is as follows:

```rust
use fars::Config;
use fars::ApiKey;
use fars::Email;

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    // 1. Create a config with your Firebase project API key.
    let config = Config::new(
        ApiKey::new("your-firebase-project-api-key"),
    );
    
    // 2. Send reset password email to specified email through the config if it has been registered.
    config.send_reset_password_email(
        Email::new("user@example"),
        None, // Option: Locale
    ).await?;

    Ok(())
}
```

## Sign in with OAuth credentials

> [!IMPORTANT]
> This crate does not provide methods to get OAuth credential for each ID provider.
>
> When you use signing in with OAuth credential, please implement a method to get target OAuth credential.

See also [supported OAuth providers](#supported-oauth-id-providers).

### Google OAuth

To sign in with Google OAuth credential, 

1. Create a config (`fars::Config`) with your Firebase project API key.
2. Get OpenID token from Google OAuth API. See [reference]https://developers.google.com/identity/protocols/oauth2/web-server#obtainingaccesstokens.
3. Sign in with specifying `request_uri` and `IdpPostBody::Google`.

A sample code to [sign in with Google OAuth credential](https://firebase.google.com/docs/reference/rest/auth#section-sign-in-with-oauth-credential) with [tokio](https://github.com/tokio-rs/tokio) and [anyhow](https://github.com/dtolnay/anyhow) is as follows:

```rust
use fars::Config;
use fars::ApiKey;
use fars::OAuthRequestUri;
use fars::IdpPostBody;

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    // 1. Create a config with your Firebase project API key.
    let config = Config::new(
        ApiKey::new("your-firebase-project-api-key"),
    );

    // 2. Get an OpenID token from Google OAuth by any method.
    let google_id_token = "google-open-id-token".to_string();

    // 3. Get a session by signing in with Google OAuth credential.
    let session = config
        .sign_in_with_oauth_credential(
            OAuthRequestUri::new("https://your-app.com/redirect/path/auth/handler"),
            IdpPostBody::Google {
                id_token: google_id_token,
            },
        )
        .await?;

    // 4. Do something with the session.

    Ok(())
}
```

## Error handling

If you handle error in this crate, please handle `fars::Result` and `fars::Error`.

> [!NOTE]
> `fars::Error::ApiError` has an error code (`fars::error::CommonErrorCode`) according to common error codes in the [API reference]https://firebase.google.com/docs/reference/rest/auth.
> You can specify error type of an API error of Firebase Auth by matching an error code (`fars::error::CommonErrorCode`).

A sample code to handle error for [signing in with email / password](https://firebase.google.com/docs/reference/rest/auth#section-sign-in-email-password) with [reqwest](https://github.com/seanmonstar/reqwest), [tokio](https://github.com/tokio-rs/tokio) and [anyhow](https://github.com/dtolnay/anyhow) is as follows:

```rust
use fars::Config;
use fars::ApiKey;
use fars::Email;
use fars::Password;

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    // Create a config.
    let config = Config::new(
        ApiKey::new("your-firebase-project-api-key"),
    );

    // Create a session by signing in with email and password.
    match config
        .sign_in_with_email_password(
            Email::new("user@example"),
            Password::new("password"),
        )
        .await
    {
        // Success
        | Ok(session) => {
            println!(
                "Succeeded to sign in with email/password: {:?}",
                session
            );
            // Do something with the session.
            Ok(())
        },
        // Failure
        | Err(error) => {
            match error {
                // Handle HTTP request error.
                | fars::Error::HttpRequestError(error) => {
                    // Do something with HTTP request error, e.g. retry.
                    Err(error.into())
                },
                // Handle API error.
                | fars::Error::ApiError {
                    status_code,
                    error_code,
                    response,
                } => {
                    match error_code {
                        | CommonErrorCode::InvalidLoginCredentials => {
                            // Do something with invalid login credentials, e.g. display error message for user: "Invalid email or/and password.".
                            Err(fars::Error::ApiError {
                                status_code,
                                error_code,
                                response,
                            }
                            .into())
                        },
                        | CommonErrorCode::UserDisabled => {
                            // Do something with disabled user, e.g. display error message for user: "This user is disabled by administrator, please use another account.".
                            Err(fars::Error::ApiError {
                                status_code,
                                error_code,
                                response,
                            }
                            .into())
                        },
                        | CommonErrorCode::TooManyAttemptsTryLater => {
                            // Do something with too many attempts, e.g. display error message for user: "Too may requests, please try again later.".
                            Err(fars::Error::ApiError {
                                status_code,
                                error_code,
                                response,
                            }
                            .into())
                        },
                        | _ => {
                            // Do something with other API errors.
                            Err(fars::Error::ApiError {
                                status_code,
                                error_code,
                                response,
                            }
                            .into())
                        },
                    }
                },
                // Handle internal errors
                | _ => {
                    // Do something with internal errors.
                    Err(error.into())
                },
            }
        },
    }
}
```

## Raw API interfaces

Provides raw [supported APIs](#supported-apis) by `fars::api` module.

A sample code to [sign in with email / password](https://firebase.google.com/docs/reference/rest/auth#section-sign-in-email-password) with [reqwest](https://github.com/seanmonstar/reqwest), [tokio](https://github.com/tokio-rs/tokio) and [anyhow](https://github.com/dtolnay/anyhow) is as follows:

```rust
use fars::ApiKey;
use fars::Client;
use fars::api;

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    // 1. Specify your API key.
    let api_key = ApiKey::new("your-firebase-project-api-key");

    // 2. Create a HTTP client.
    let client = Client::new();

    // 3. Create a request payload for the sign in API.
    let request_payload = api::SignInWithEmailPasswordRequestBodyPayload::new(
        "user@example.com".to_string(),
        "password".to_string(),
    );

    // 4. Send a request and receive a response payload of the sign in API.
    let response_payload = api::sign_in_with_email_password(
        &client,
        &api_key,
        request_payload,
    ).await?;

    // 5. Do something with the response payload.

    Ok(())
}
```

## (Optional) ID token verification

Provides ID token verification of the Firebase Auth via `fars::verification` module.

> [!NOTE]
> ID token verification is an optional feature.
> 
> Please activate this feature by CLI:
> 
> ```shell
> $ cargo add fars --features verify
> ```
> 
> or adding features to your `Cargo.toml`:
> 
> ```toml
> [dependencies]
> fars = { version = "0.2.0", features = ["verify"] }
> ```

A sample code to [verify ID token](https://firebase.google.com/docs/auth/admin/verify-id-tokens#verify_id_tokens_using_a_third-party_jwt_library) with [tokio](https://github.com/tokio-rs/tokio) and [anyhow](https://github.com/dtolnay/anyhow) is as follows:

```rust
use fars::VerificationConfig;
use fars::ProjectId;
use fars::IdToken;

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    // Create a cofig for verification with your Firebase project ID.
    let cofing = let config = VerificationConfig::new(
        ProjectId::new("firebase-project-id"),
    );

    // Get an ID token of the Firebase Auth by any method.
    let id_token = IdToken::new("id-token");

    // Verrify the ID token.
    match config.verify_id_token(&id_token).await {
        Ok(claims) => {
            // Verification succeeded.
        },
        Err(error) => {
            // Verification failed.
        },
    }

    Ok(())
}
```

## HTTP client customization

Provides HTTP client customization interface for Firebase Auth APIs.

> [!NOTE]
> HTTP client customization is an optional feature.
> 
> Please activate this feature by CLI:
> 
> ```shell
> $ cargo add fars --features custom_client
> ```
> 
> or adding features to your `Cargo.toml`:
> 
> ```toml
> [dependencies]
> fars = { version = "0.2.0", features = ["custom_client"] }
> ```

An example to customize timeout options of HTTP client with [tokio](https://github.com/tokio-rs/tokio) and [anyhow](https://github.com/dtolnay/anyhow) is as follows:

```rust
use std::time::Duration;
use fars::Client;
use fars::ApiKey;
use fars::Config;

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    // 1. Create a custom client with re-exported `reqwest` client.
    let client = fars::reqwest::ClientBuilder::new()
        .timeout(Duration::from_secs(60))
        .connect_timeout(Duration::from_secs(10))
        .build()?;

    // 2. Customize HTTP client.
    let client = Client::custom(client);

    // 3. Create a cofig with customized client.
    let config = Config::custom(
        ApiKey::new("your-firebase-project-api-key"),
        client,
    );

    // 4. Do something with a customized config.

    Ok(())
}
```

## Other examples

Please refer [/examples](./examples/) directory, [a shell script](./examples.sh) and [a study of authentication on Web frontend](https://github.com/mochi-neko/rust-frontend-playground) with [dioxus](https://github.com/DioxusLabs/dioxus).

## Changelog

See [CHANGELOG](./CHANGELOG.md).

## License

Licensed under either of the [Apache License, Version 2.0](./LICENSE-APACHE) or the [MIT](./LICENSE-MIT) license at your option.