Skip to main content

GraphQLContext

Struct GraphQLContext 

Source
pub struct GraphQLContext {
    pub pool: PgPool,
    pub schema_cache: SchemaCacheRef,
    pub auth: AuthResult,
    pub session: HashMap<String, String>,
    pub hasura_role: Option<String>,
    pub elevated: bool,
    pub write: SharedWrite,
    pub config: Arc<AppConfig>,
}
Expand description

Context available to all GraphQL resolvers.

Fields§

§pool: PgPool

Database connection pool.

§schema_cache: SchemaCacheRef

Schema cache for table/column metadata.

§auth: AuthResult

Authentication result with role and claims.

§session: HashMap<String, String>

Session variables carried into the transaction, without their x-hasura- prefix and lowercased: user_id, org_id.

This is the half of Hasura’s permission model that transfers. Its rules live in metadata and are compiled into every query; here permissions live in the database as roles and row level security, and what a policy needs is the caller’s identity. A policy written against current_setting('hasura.user_id') sees what the Hasura permission would have seen.

§hasura_role: Option<String>

The role the caller speaks as, in Hasura’s sense, where that is not the database role the transaction runs as.

The two are different things and conflating them was the reason hasura_session->>'x-hasura-role' used to answer with the name of the connecting database user. Hasura’s roles are labels – Artist, anonymous – that exist in no catalogue and could not be SET ROLE to; what they decide is which permissions apply and what a function reading the session document sees. Which database user the transaction runs as is AuthResult::role, and it is settled separately.

None when nothing named a Hasura role, in which case the database role stands in for it, as it did before the two were told apart.

§elevated: bool

Whether the admin secret authenticated this request.

Not the same question as whether the role is admin, which an administrator gives up as soon as it asks to be treated as someone else. What this gates is the fields a backend_only permission hides: reachable by a caller that proved it holds the secret, whatever role it then claims, and by no one else.

§write: SharedWrite

The transaction every write in this operation shares. See SharedWrite.

§config: Arc<AppConfig>

The server’s configuration, which a transaction is set up from.

What Self::session reads beyond the caller: a role’s own statement_timeout and isolation level, the server-wide isolation level and app_settings. The same configuration REST’s transactions are set up from, so a function or a policy sees the same thing whichever of the two called it.

Implementations§

Source§

impl GraphQLContext

Source

pub fn new(pool: PgPool, schema_cache: SchemaCacheRef, auth: AuthResult) -> Self

Create a new GraphQL context.

Source

pub fn with_config(self, config: Arc<AppConfig>) -> Self

Set up this request’s transactions from config.

Without it they are set up from AppConfig::default: PostgREST’s defaults, and no role settings.

Source

pub fn with_identity(self, role: Option<String>, elevated: bool) -> Self

Record who the caller is in Hasura’s sense, and whether it proved it.

Source

pub fn acting_role(&self) -> &str

The role the caller speaks as: the Hasura role where one was named, and the database role otherwise.

Source

pub fn caller(&self) -> Caller<'_>

Who is asking, for the SQL builders that have to narrow rows to them.

The Hasura role rather than Self::acting_role: a permission is keyed by a role someone wrote into a document, and the database role standing in for a missing one would name no permission and read as unrestricted – which is right, and is worth being explicit about.

Source

pub fn with_write(self, write: SharedWrite) -> Self

Share one transaction with whoever is going to settle it.

The caller keeps the other handle: it is the only thing that knows whether the operation ended in errors, and so the only thing that can decide between commit and rollback.

Source

pub fn with_session(self, session: HashMap<String, String>) -> Self

Carry session variables into every transaction this request opens.

Source

pub fn session(&self) -> Session<'_>

What makes a transaction this request’s: the statements that turn a pooled connection into the caller.

The same Session a REST request’s transaction is set up by, so the two cannot drift apart: one SELECT set_config(...) carrying the role’s own statement_timeout, the database role, the verified JWT as request.jwt.claims and request.jwt.claims.<name>, and the app.settings.* – and then, last, the hasura.* session variables, which no REST request has. Ahead of it, the isolation level where the role’s setting or PGRST_DB_TX_ISOLATION asks for one other than PostgreSQL’s default.

The role is Self::role, the database role, never the Hasura role a caller speaks as – which exists in no catalogue and goes in only as hasura.role and inside hasura.session.

Unlike REST, the search_path is left as the role and the database set it, as GraphQL has always left it and as Hasura does. An operation may touch every exposed schema, and the SQL generated for one names each table it reads with its schema, so the path decides nothing about which table a field reads. What it does decide is where the unqualified calls in that SQL – PostGIS’s ST_AsGeoJSON among them – are found. Setting REST’s path would stop finding an extension installed outside db_extra_search_path (in extensions, say, as Supabase installs them) that the role’s own path finds, and a GraphQL query that worked would stop working.

Nothing about the HTTP request goes in. request.headers would carry the admin secret, which postrust_auth::hasura keeps out of every setting a function can read; request.method and request.path are the same for every operation and tell a function nothing. And db_pre_request is not called: it is a hook on REST requests, written against the request.* settings it would find missing here, and GraphQL has never run it.

Read-write, as a GraphQL transaction always has been, for a query as for a mutation. A query’s SQL is a SELECT, and the query root serves no volatile function, so SET TRANSACTION READ ONLY would refuse little – but whatever it did refuse, a volatile function a computed field calls say, is an answer that changes, and that is a change of its own to make and measure rather than one to carry in with this.

Source

pub fn hasura_session(&self) -> Value

The session as a function reading hasura_session expects it.

Hasura hands a function a JSON object of the caller’s session variables, under the names they arrive as – x-hasura-role, x-hasura-user-id – which is what a function body indexes into. Self::session holds them stripped and lowercased for the settings a row-level policy reads, so the prefix goes back on here.

The role is included and is not the caller’s to choose: it is what authenticated the request, or the role an authenticated administrator asked to be treated as – never a bare header.

Source

pub fn role(&self) -> &str

The database role this request’s transaction runs as.

Not Self::acting_role, which is who the caller says it is. The session’s role setting takes this one, because it is the only one of the two that has to exist in the catalogue.

Source

pub fn claim(&self, key: &str) -> Option<&Value>

Get a claim value.

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<A, B, T> HttpServerConnExec<A, B> for T
where B: Body,

Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self> ⓘ

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self> ⓘ

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ

Converts self into a Left variant of Either<Self, Self> if into_left is true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
where F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> if into_left(&self) returns true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
Source§

impl<T> MaybeSend for T
where T: Send + ?Sized,

Source§

impl<T> MaybeSendSync for T
where T: Send + Sync + ?Sized,

Source§

impl<T> MaybeSync for T
where T: Sync + ?Sized,

Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = !

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, !>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<V, T> VZip<V> for T
where V: MultiLane<T>,

Source§

fn vzip(self) -> V

Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self> ⓘ
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self> ⓘ

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more