pub trait ExternalAuth:
Send
+ Sync
+ 'static {
type User: PanelUser;
// Required methods
fn current_user(
&self,
cx: &Cx,
) -> impl Future<Output = Result<Option<Self::User>>> + Send;
fn login_url(&self, cx: &Cx, next: &str) -> String;
fn logout_url(&self, cx: &Cx) -> String;
}Expand description
A host app’s own sign-in, which a panel trusts in place of a login form (ADR-0013).
Auth::external installs it. The app keeps its login page, its
sessions and its credentials; a panel user is whoever
current_user says the request’s app session belongs to.
An anonymous request to the panel’s {prefix}/login goes to
login_url. Once the app has signed the user in and sent them back, the
panel starts a panel session of its own, which holds what the panel keeps per browser, such as
the selected tenant. On every later request the gate asks current_user again and serves a
session only while it names the session’s user: signing out of the app, or into another
account, takes effect on the panel’s next request.
Each panel keeps its session in a cookie of its own, so entering this panel leaves a session held on another as it was.
use tablo_core::{ExternalAuth, PanelUser, auth::safe_next};
use topcoat::{context::Cx, session};
struct Staff {
id: String,
name: String,
admin: bool,
}
impl PanelUser for Staff {
fn user_id(&self) -> String {
self.id.clone()
}
fn display_name(&self) -> &str {
&self.name
}
fn can_access_panel(&self, _panel: &str) -> bool {
self.admin
}
}
async fn staff_for(_cx: &Cx, _hash: &session::TokenHash) -> topcoat::Result<Option<Staff>> {
Ok(None) // the app's own session lookup
}
struct AppSession;
impl ExternalAuth for AppSession {
type User = Staff;
async fn current_user(&self, cx: &Cx) -> topcoat::Result<Option<Staff>> {
match session::token_hash(cx).await? {
Some(hash) => staff_for(cx, &hash).await,
None => Ok(None),
}
}
fn login_url(&self, _cx: &Cx, next: &str) -> String {
format!("/login?return_to={next}")
}
fn logout_url(&self, _cx: &Cx) -> String {
"/sign-out".to_owned()
}
}Required Associated Types§
Required Methods§
Sourcefn current_user(
&self,
cx: &Cx,
) -> impl Future<Output = Result<Option<Self::User>>> + Send
fn current_user( &self, cx: &Cx, ) -> impl Future<Output = Result<Option<Self::User>>> + Send
The user the request’s app session signs in; None when it signs in nobody.
It runs on the gate of every panel request, and again when a live connection checks its
session, long after the response started: it reads the request and never writes a cookie,
so a sliding app session refreshes elsewhere. A live connection keeps the cookies of its
handshake, so to end a session at once it must consult state the server holds: a signed
cookie alone keeps naming its user until it expires. The app’s session cookie must also
reach a page opened from another site, as SameSite=Lax does, or a followed link finds no
user. A failure to reach the app’s store returns the error, which the panel logs and
answers with an opaque 500 rather than as a signed-out user.
Sourcefn login_url(&self, cx: &Cx, next: &str) -> String
fn login_url(&self, cx: &Cx, next: &str) -> String
Where a request with no app session signs in. next is the URL to come back to,
percent-encoded to sit in a query string. Check it with
safe_next before redirecting to it: the browser can send
anything.
Sourcefn logout_url(&self, cx: &Cx) -> String
fn logout_url(&self, cx: &Cx) -> String
Where the shell’s Sign out goes, once it has ended the panel’s session. That ends only the panel’s, so while the app’s lives the user’s next panel request signs them in again. Name a route that ends the app’s session, or a page of the app’s that offers to: the browser reaches it by GET, and it may lie on another origin, such as an SSO provider’s.
Dyn Compatibility§
This trait is not dyn compatible.
In older versions of Rust, dyn compatibility was called "object safety".