//! [<img alt="github" src="https://img.shields.io/badge/github-udoprog/watchword-8da0cb?style=for-the-badge&logo=github" height="20">](https://github.com/udoprog/watchword)
//! [<img alt="crates.io" src="https://img.shields.io/crates/v/watchword.svg?style=for-the-badge&color=fc8d62&logo=rust" height="20">](https://crates.io/crates/watchword)
//! [<img alt="docs.rs" src="https://img.shields.io/badge/docs.rs-watchword-66c2a5?style=for-the-badge&logoColor=white&logo=data:image/svg+xml;base64,PHN2ZyByb2xlPSJpbWciIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgdmlld0JveD0iMCAwIDUxMiA1MTIiPjxwYXRoIGZpbGw9IiNmNWY1ZjUiIGQ9Ik00ODguNiAyNTAuMkwzOTIgMjE0VjEwNS41YzAtMTUtOS4zLTI4LjQtMjMuNC0zMy43bC0xMDAtMzcuNWMtOC4xLTMuMS0xNy4xLTMuMS0yNS4zIDBsLTEwMCAzNy41Yy0xNC4xIDUuMy0yMy40IDE4LjctMjMuNCAzMy43VjIxNGwtOTYuNiAzNi4yQzkuMyAyNTUuNSAwIDI2OC45IDAgMjgzLjlWMzk0YzAgMTMuNiA3LjcgMjYuMSAxOS45IDMyLjJsMTAwIDUwYzEwLjEgNS4xIDIyLjEgNS4xIDMyLjIgMGwxMDMuOS01MiAxMDMuOSA1MmMxMC4xIDUuMSAyMi4xIDUuMSAzMi4yIDBsMTAwLTUwYzEyLjItNi4xIDE5LjktMTguNiAxOS45LTMyLjJWMjgzLjljMC0xNS05LjMtMjguNC0yMy40LTMzLjd6TTM1OCAyMTQuOGwtODUgMzEuOXYtNjguMmw4NS0zN3Y3My4zek0xNTQgMTA0LjFsMTAyLTM4LjIgMTAyIDM4LjJ2LjZsLTEwMiA0MS40LTEwMi00MS40di0uNnptODQgMjkxLjFsLTg1IDQyLjV2LTc5LjFsODUtMzguOHY3NS40em0wLTExMmwtMTAyIDQxLjQtMTAyLTQxLjR2LS42bDEwMi0zOC4yIDEwMiAzOC4ydi42em0yNDAgMTEybC04NSA0Mi41di03OS4xbDg1LTM4Ljh2NzUuNHptMC0xMTJsLTEwMiA0MS40LTEwMi00MS40di0uNmwxMDItMzguMiAxMDIgMzguMnYuNnoiPjwvcGF0aD48L3N2Zz4K" height="20">](https://docs.rs/watchword)
//!
//! Authentication for axum servers and Yew frontends: password, Cloudflare
//! Access and Google sign-in, sessions and user management.
//!
//! The design, including the full API each layer grows into, is in
//! [`docs/design.md`].
//!
//! <br>
//!
//! ## Features
//!
//! Each layer sits behind a cargo feature, so an app takes only what it uses.
//! Server-only and browser-only dependencies never share a feature, so a wasm
//! frontend can take `yew023` without pulling in server code. Only `std` is on
//! by default. On docs.rs, items that need a feature are marked with it.
//!
//! The crate is `no_std`. With `default-features = false` and neither `std`
//! nor `alloc`, it has only the plain types (`UserId`, `Role`, `Access`,
//! `Method`, `Conflict`, `AuthConfig`, `MIN_PASSWORD_LEN`, `is_same_origin_path`); `alloc` adds the
//! rest of [`types`]. The server layers and `yew023` imply `std`.
//!
//! A dependency that is not yet 1.0 and appears in the API is named after its
//! version: its feature (and module, if it has one) is `axum08` for axum
//! 0.8, `sqll015` for sqll 0.15, `musli02` for musli 0.2, `musliweb05` for
//! musli-web 0.5, `phrasebookcatalog01` for phrasebook-catalog 0.1 and
//! `yew023` for yew 0.23. A later version is added beside it, so a watchword
//! upgrade never forces an app onto a new axum, sqll, musli, musli-web,
//! phrasebook-catalog or yew,
//! and an app moves when it chooses.
//!
//! | Feature | Module | What it provides |
//! |---|---|---|
//! | *(none)* | [`types`] | `UserId`, `Role`, `Access`, `Method`, `Conflict`, `AuthConfig`, `MIN_PASSWORD_LEN` and `is_same_origin_path`, from the [watchword-core] crate |
//! | `alloc` | [`types`] | the wire types (`UserInfo`, `LoginRequest`, `CreatedUser`, ...), `AuthError` and its `Invalid` reasons, `validate_password`, `normalize_email` |
//! | `std` *(default)* | | `alloc`, and the standard library for the dependencies that have it |
//! | `musli02` | | musli 0.2 `Encode`/`Decode` on the types |
//! | `musliweb05` | [`musliweb05`] | for musli-web 0.5: the account, user-management and Cloudflare Access settings endpoints and the `AuthBroadcast` of live auth changes, as API types an app carries on its own musli-web WebSocket, with their ids pinned in 32000–32255; with `axum08`, `Connection`, which answers those endpoints on one socket and turns `AuthEvent`s into the updates it may see; with `yew023`, `AuthContext::set_socket`, through which the components follow those broadcasts (implies `alloc` and `musli02`) |
//! | `server` | [`core`], [`store`] | bcrypt password hashing with `dummy_hash` timing, session ids and link tokens, HMAC-signed session cookies; the `Auth` service (sign-in, registration links, user management, sign-in throttle, `AuthEvent`s and actor-attributed `AuditEvent`s, bootstrap admin) and `Auth::guard` for WebSocket sessions; the `Store` trait and `MemoryStore` (implies `std`) |
//! | `cloudflare` | [`cloudflare`] | Cloudflare Access JWT verification with a pluggable `KeyFetcher`; `Auth::cloudflare` and `Auth::set_cloudflare`, which keep the settings in the store; with `axum08`, admin routes for them (implies `server`) |
//! | `google` | [`google`] | Google OAuth: `GoogleConfig::new` for `AuthBuilder::google`; with `axum08`, `routes()` serves its start, registration, account-switch and callback routes (implies `server`) |
//! | `axum08` | [`axum08`] | for axum 0.8: the `SignedIn`, `Admin`, `MaybeSignedIn` and `LiveSession` extractors and `routes()` for the `/api/auth` JSON routes, plus the Cloudflare and Google routes when those features are on (implies `server`) |
//! | `sqll015` | | sqll 0.15 `BindValue`, `Bind` and `FromColumn` on `UserId` (an integer with the same bits), `Role`, `Access` and `Method` (their names), from the [watchword-core] crate, for an app's own `Store` over SQLite such as [`examples/sqll-store`]; no storage of its own |
//! | `test-util` | [`store_suite!`], [`google::fake`] | for an app's tests: `Auth::test_user` and `Auth::test_session`, an active user and a session without signing in; `store_suite!`, watchword's store conformance suite, to run over the app's own `Store`; with `google`, `AuthBuilder::google_endpoints`, and with `google` and `axum08` also `FakeGoogle`, a local fake Google to sign in through offline (implies `server`) |
//! | `phrasebookcatalog01` | [`phrasebookcatalog01`] | `L10N`, watchword's messages (the text of the `yew023` components) as a [phrasebook-catalog] 0.1 catalog, which [phrasebook] 0.2 takes, for a native tool such as an app's localization checker that needs them without building yew, from the [watchword-core] crate (needs neither `std` nor `alloc`) |
//! | `yew023` | [`yew023`] | for yew 0.23: `AuthProvider`, `use_auth`, `SignIn`, `Register`, `AccountPanel`, `UserMenu`, `AdminUsers`, `AdminCloudflare`, `CloudflareSettings`, the `http` client and `L10N`, the components' messages as a [phrasebook] catalog, from the [watchword-yew023] crate (implies `std` and `phrasebookcatalog01`) |
//!
//! The most-used names that do not depend on a versioned feature are
//! re-exported at the crate root.
//!
//! <br>
//!
//! ## Using it
//!
//! Add it from crates.io with the features each crate needs. An app whose
//! frontend talks to its server over a [musli-web] 0.5 WebSocket, as the
//! [example app](#example-app) does, takes `musliweb05` on both sides: the
//! server answers the components' requests and sends them live updates on
//! that socket, and the frontend hands the socket to the components. An app
//! that uses only HTTP leaves it out of both.
//!
//! ```toml
//! # The server crate.
//! [dependencies]
//! watchword = { version = "0.3", features = ["axum08", "cloudflare", "google", "musliweb05"] }
//!
//! [dev-dependencies]
//! watchword = { version = "0.3", features = ["test-util"] }
//! ```
//!
//! ```toml
//! # The frontend crate, built for wasm32-unknown-unknown.
//! [dependencies]
//! watchword = { version = "0.3", features = ["yew023", "musliweb05"] }
//! ```
//!
//! Take only the sign-in methods the app offers: `cloudflare` and `google`
//! each add one, and password sign-in is always there. `test-util`, under
//! `dev-dependencies`, gives the app's tests users and sessions without going
//! through sign-in, and the store conformance suite.
//!
//! <br>
//!
//! ## Storage
//!
//! watchword keeps users, sessions, registration links and settings through
//! the [`Store`] trait, which each app implements over its own database, with
//! its own tables and migrations: the shape of user data differs between
//! apps, and only the app knows how to fill its own columns. watchword ships
//! no tables or migrations, only `MemoryStore`, which keeps everything in
//! memory, for tests and trying things out.
//!
//! Start from [`examples/sqll-store`], a complete `Store` over SQLite with
//! sqll 0.15 (taking the `sqll015` feature), and change its tables to the
//! app's. The [`Store`] docs say what each method must do atomically, and the
//! store conformance suite, [`store_suite!`] (feature `test-util`), checks
//! the result from the app's tests:
//!
//! ```sh
//! cargo test --example sqll-store --features test-util,sqll015
//! ```
//!
//! The examples here refer to the crate as `watchword`. An app may give the
//! dependency a shorter key, such as `auth = { package = "watchword", ... }`,
//! and then writes `auth::` where these docs write `watchword::`.
//!
//! Then build an `Auth` over a store and serve its routes, as the [`axum08`]
//! module shows, and wrap the frontend in `AuthProvider`, as the [`yew023`]
//! module shows.
//!
//! <br>
//!
//! ## Example app
//!
//! [`examples/app`] is an axum server and a Yew frontend wired up with all three
//! sign-in methods, `MemoryStore` and a musli-web WebSocket held open for the
//! session, which carries the components' account and admin requests and live
//! updates beside the app's own API (`musliweb05`): a reference for apps taking
//! the library, and a way to try the components by hand. It is its own cargo
//! workspace, so it adds nothing to the library's builds or lockfile.
//!
//! Build the frontend with [trunk], then run the server, which serves it:
//!
//! ```sh
//! cd examples/app/frontend && trunk build
//! cd examples/app && cargo run -p watchword-example-server -- --bind 127.0.0.1:8080
//! ```
//!
//! The server prints a registration link for the bootstrap admin (`admin` by
//! default, `--bootstrap-admin NAME` to change it); open it to choose a password
//! and sign in. Everything is in memory, so a restart starts over. Google sign-in
//! is on when `GOOGLE_CLIENT_ID` and `GOOGLE_CLIENT_SECRET` are set, with the
//! OAuth client's redirect URL at `PUBLIC_URL/api/auth/google/redirect`
//! (`--public-url`, by default `http://` plus the bind address). Cloudflare Access
//! is configured on the app's Settings page, which watchword serves, or from
//! `CLOUDFLARE_TEAM_DOMAIN` and `CLOUDFLARE_AUDIENCE` until saved there; `CLOUDFLARE_TRUST_EMAIL_HEADER=1` accepts Access's email
//! header alone, for local testing only. `--help` lists the options.
//!
//! trunk builds with the `wasm-bindgen` CLI matching `examples/app/Cargo.lock`,
//! downloading it unless it is installed or already in trunk's cache.
//!
//! [`axum08`]: https://docs.rs/watchword/latest/watchword/axum08/index.html
//! [`cloudflare`]: https://docs.rs/watchword/latest/watchword/cloudflare/index.html
//! [`core`]: https://docs.rs/watchword/latest/watchword/core/index.html
//! [`docs/design.md`]: https://github.com/udoprog/watchword/blob/main/docs/design.md
//! [`examples/app`]: https://github.com/udoprog/watchword/tree/main/examples/app
//! [`examples/sqll-store`]: https://github.com/udoprog/watchword/tree/main/examples/sqll-store
//! [`google::fake`]: https://docs.rs/watchword/latest/watchword/google/fake/index.html
//! [`google`]: https://docs.rs/watchword/latest/watchword/google/index.html
//! [`musliweb05`]: https://docs.rs/watchword/latest/watchword/musliweb05/index.html
//! [`phrasebookcatalog01`]: https://docs.rs/watchword/latest/watchword/phrasebookcatalog01/index.html
//! [`store_suite!`]: https://docs.rs/watchword/latest/watchword/macro.store_suite.html
//! [`store`]: https://docs.rs/watchword/latest/watchword/store/index.html
//! [`Store`]: https://docs.rs/watchword/latest/watchword/store/trait.Store.html
//! [`types`]: https://docs.rs/watchword/latest/watchword/types/index.html
//! [`yew023`]: https://docs.rs/watchword/latest/watchword/yew023/index.html
//! [musli-web]: https://docs.rs/musli-web
//! [phrasebook-catalog]: https://docs.rs/phrasebook-catalog
//! [phrasebook]: https://docs.rs/phrasebook
//! [trunk]: https://trunkrs.dev
//! [watchword-core]: https://docs.rs/watchword-core
//! [watchword-yew023]: https://docs.rs/watchword-yew023
#![no_std]
#![cfg_attr(watchword_docsrs, feature(doc_cfg))]
#[cfg(feature = "alloc")]
extern crate alloc;
// The server layers run on tokio and axum, so they all imply `std`, and use
// its macros.
#[cfg(feature = "std")]
#[cfg_attr(feature = "server", macro_use)]
extern crate std;
#[doc(inline)]
pub use watchword_core as types;
#[cfg(feature = "musliweb05")]
#[cfg_attr(watchword_docsrs, doc(cfg(feature = "musliweb05")))]
pub mod musliweb05;
#[cfg(feature = "server")]
#[cfg_attr(watchword_docsrs, doc(cfg(feature = "server")))]
pub mod core;
#[cfg(feature = "server")]
#[cfg_attr(watchword_docsrs, doc(cfg(feature = "server")))]
pub mod store;
#[cfg(feature = "cloudflare")]
#[cfg_attr(watchword_docsrs, doc(cfg(feature = "cloudflare")))]
pub mod cloudflare;
#[cfg(feature = "google")]
#[cfg_attr(watchword_docsrs, doc(cfg(feature = "google")))]
pub mod google;
#[cfg(feature = "axum08")]
#[cfg_attr(watchword_docsrs, doc(cfg(feature = "axum08")))]
pub mod axum08;
#[cfg(feature = "phrasebookcatalog01")]
#[cfg_attr(watchword_docsrs, doc(cfg(feature = "phrasebookcatalog01")))]
#[doc(inline)]
pub use watchword_core::phrasebookcatalog01;
#[cfg(feature = "yew023")]
#[cfg_attr(watchword_docsrs, doc(cfg(feature = "yew023")))]
#[doc(inline)]
pub use watchword_yew023 as yew023;
#[cfg(all(test, feature = "yew023", feature = "server"))]
mod yew023_tests;
pub use self::types::{
Access, AuthConfig, Conflict, MIN_PASSWORD_LEN, Method, Role, UserId, is_same_origin_path,
};
#[cfg(feature = "alloc")]
#[cfg_attr(watchword_docsrs, doc(cfg(feature = "alloc")))]
pub use self::types::{
AuthError, CloudflareConfig, CreatedUser, Impersonator, Invalid, Link, LoginRequest, NewUser,
PasswordChange, RegisterInfo, RegisterRequest, UserChange, UserEntry, UserInfo,
normalize_email, validate_password,
};
#[cfg(feature = "server")]
#[cfg_attr(watchword_docsrs, doc(cfg(feature = "server")))]
pub use self::core::{
Actor, AuditEvent, Auth, AuthBuilder, AuthEvent, Bootstrap, BootstrapAdmin, CookieConfig,
PasswordError, SESSION_LIFETIME, Secure, SessionGuard, TOKEN_LIFETIME, dummy_hash,
generate_session_key, hash_password, is_expired, load_or_create_key, new_session_id, new_token,
session_cookie, session_expiry, token_expiry, verify_password, verify_session_cookie,
};
#[cfg(feature = "server")]
#[cfg_attr(watchword_docsrs, doc(cfg(feature = "server")))]
pub use self::store::{MemoryStore, Store};
#[cfg(feature = "google")]
#[cfg_attr(watchword_docsrs, doc(cfg(feature = "google")))]
pub use self::google::{GoogleConfig, GoogleConfigError};