grafbase_sdk/extension/authentication.rs
1use crate::{
2 component::AnyExtension,
3 types::{Configuration, Error, ErrorResponse, GatewayHeaders, PublicMetadataEndpoint, RequestContext, Token},
4};
5
6/// An authentication extension is called before any request processing, authenticating a user with
7/// a token or returning an error response.
8///
9/// # Example
10///
11/// You can initialize a new authentication extension with the Grafbase CLI:
12///
13/// ```bash
14/// grafbase extension init --type authentication my-auth
15/// ```
16///
17/// This will generate the following:
18///
19/// ```rust
20/// use grafbase_sdk::{
21/// AuthenticationExtension,
22/// types::{GatewayHeaders, Configuration, ErrorResponse, Token, Error, RequestContext}
23/// };
24///
25/// #[derive(AuthenticationExtension)]
26/// struct MyAuth {
27/// config: Config
28/// }
29///
30/// #[derive(serde::Deserialize)]
31/// struct Config {
32/// my_custom_key: String
33/// }
34///
35/// impl AuthenticationExtension for MyAuth {
36/// fn new(config: Configuration) -> Result<Self, Error> {
37/// let config: Config = config.deserialize()?;
38/// Ok(Self { config })
39/// }
40///
41/// fn authenticate(&mut self, ctx: &RequestContext, headers: &GatewayHeaders) -> Result<Token, ErrorResponse> {
42/// todo!()
43/// }
44/// }
45/// ```
46/// ## Configuration
47///
48/// The configuration provided in the `new` method is the one defined in the `grafbase.toml`
49/// file by the extension user:
50///
51/// ```toml
52/// [extensions.my-auth.config]
53/// my_custom_key = "value"
54/// ```
55///
56/// Once your business logic is written down you can compile your extension with:
57///
58/// ```bash
59/// grafbase extension build
60/// ```
61///
62/// It will generate all the necessary files in a `build` directory which you can specify in the
63/// `grafbase.toml` configuration with:
64///
65/// ```toml
66/// [extensions.my-auth]
67/// path = "<project path>/build"
68/// ```
69///
70pub trait AuthenticationExtension: Sized + 'static {
71 /// Creates a new instance of the extension. The [Configuration] will contain all the
72 /// configuration defined in the `grafbase.toml` by the extension user in a serialized format.
73 ///
74 /// # Example
75 ///
76 /// The following TOML configuration:
77 /// ```toml
78 /// [extensions.my-auth.config]
79 /// my_custom_key = "value"
80 /// ```
81 ///
82 /// can be easily deserialized with:
83 ///
84 /// ```rust
85 /// # use grafbase_sdk::types::{Configuration, Error};
86 /// # fn dummy(config: Configuration) -> Result<(), Error> {
87 /// #[derive(serde::Deserialize)]
88 /// struct Config {
89 /// my_custom_key: String
90 /// }
91 ///
92 /// let config: Config = config.deserialize()?;
93 /// # Ok(())
94 /// # }
95 /// ```
96 fn new(config: Configuration) -> Result<Self, Error>;
97
98 /// Authenticate the user with a [Token] or return an [ErrorResponse]. It is called before any
99 /// GraphQL processing and an error will stop any further actions.
100 ///
101 /// The [HttpHeaders] are the headers received by the gateway before any header rules.
102 fn authenticate(&mut self, ctx: &RequestContext, headers: &GatewayHeaders) -> Result<Token, ErrorResponse>;
103
104 /// Define endpoints on the gateway that expose authentication related metadata. This can be used to implement [OAuth 2.0 Protected Resource Metadata](https://datatracker.ietf.org/doc/html/rfc9728), for example. This method is only called once, on gateway initialization. The endpoints are available on the gateway for GET requests at a custom path, and they return a static payload with custom headers.
105 ///
106 /// See the docs on [`public-metadata-endpoint`](public-metadata-endpoint) for details.
107 fn public_metadata(&mut self) -> Result<Vec<PublicMetadataEndpoint>, Error> {
108 Ok(vec![])
109 }
110}
111
112#[doc(hidden)]
113pub fn register<T: AuthenticationExtension>() {
114 pub(super) struct Proxy<T: AuthenticationExtension>(T);
115
116 impl<T: AuthenticationExtension> AnyExtension for Proxy<T> {
117 fn authenticate(&mut self, ctx: &RequestContext, headers: &GatewayHeaders) -> Result<Token, ErrorResponse> {
118 self.0.authenticate(ctx, headers)
119 }
120
121 fn public_metadata(&mut self) -> Result<Vec<PublicMetadataEndpoint>, Error> {
122 AuthenticationExtension::public_metadata(&mut self.0)
123 }
124 }
125
126 crate::component::register_extension(Box::new(|_, config| {
127 <T as AuthenticationExtension>::new(config).map(|extension| Box::new(Proxy(extension)) as Box<dyn AnyExtension>)
128 }))
129}