Documentation
¶
Overview ¶
Package auth handles the auth of gRPC server.
Index ¶
- Constants
- Variables
- func AccessTokenAudience(mode common.ReleaseMode) string
- func GenerateAPIToken(userName string, userID int, mode common.ReleaseMode, secret string) (string, error)
- func GenerateAccessToken(userName string, userID int, mode common.ReleaseMode, secret string, ...) (string, error)
- func GenerateMCPAccessToken(userName string, userID int, clientID, audience, scope, secret string, ...) (string, error)
- func GenerateRefreshToken() (string, error)
- func GenerateRestrictedAccessToken(userName string, userID int, mode common.ReleaseMode, secret string, ...) (string, error)
- func GetRefreshTokenCookie(ctx context.Context, stores *store.Store, token string, expiresAt time.Time) *http.Cookie
- func GetRefreshTokenDuration(_ context.Context, _ *store.Store) time.Duration
- func GetRefreshTokenFromCookie(headers http.Header) string
- func GetTokenCookie(ctx context.Context, stores *store.Store, token string) *http.Cookie
- func GetTokenDuration(_ context.Context, _ *store.Store) time.Duration
- func GetTokenFromHeaders(headers http.Header) (string, error)
- func HashToken(token string) string
- func IsAuthenticationAllowed(_ string, authContext *common.AuthContext) bool
- func TokenPredatesPasswordChange(issuedAt, changedAt time.Time) bool
- type APIAuthInterceptor
- func (*APIAuthInterceptor) WrapStreamingClient(next connect.StreamingClientFunc) connect.StreamingClientFunc
- func (in *APIAuthInterceptor) WrapStreamingHandler(next connect.StreamingHandlerFunc) connect.StreamingHandlerFunc
- func (in *APIAuthInterceptor) WrapUnary(next connect.UnaryFunc) connect.UnaryFunc
- type AccessTokenIdentity
- type GatewayResponseModifier
- type TokenAuthenticator
- type TokenRestriction
- type UserStore
Constants ¶
const ( // AccessTokenAudienceFmt is the format of the acccess token audience. AccessTokenAudienceFmt = "mt.user.access.%s" // DefaultTokenDuration is the default token expiration duration. DefaultTokenDuration = 7 * 24 * time.Hour // DefaultRefreshTokenDuration is how long a refresh token stays usable. It is // deliberately longer than an access token: it is the credential that // outlives a session, and single-use rotation means a stolen copy is // worthless once the real holder refreshes. DefaultRefreshTokenDuration = 30 * 24 * time.Hour // AccessTokenCookieName is the cookie name of access token. AccessTokenCookieName = "access-token" // RefreshTokenCookieName is the cookie name of the web session's refresh // token. Like the access token it is HttpOnly, so page JavaScript can never // read either. RefreshTokenCookieName = "refresh-token" )
Variables ¶
var ( // ErrTokenMissing means the request carried no bearer token. ErrTokenMissing = errs.New("access token not found") // ErrTokenRevoked means the token was revoked, or has expired. ErrTokenRevoked = errs.New("access token expired") // ErrTokenInvalid means the token did not verify for the expected audience. ErrTokenInvalid = errs.New("failed to parse claim") )
Token failures a TokenAuthenticator reports. They are plain errors rather than ConnectRPC errors so that each entry point can map them onto its own transport's error shape while applying the same rules.
Functions ¶
func AccessTokenAudience ¶
func AccessTokenAudience(mode common.ReleaseMode) string
AccessTokenAudience returns the audience an access token minted for the user API must carry in the given release mode.
func GenerateAPIToken ¶
func GenerateAPIToken(userName string, userID int, mode common.ReleaseMode, secret string) (string, error)
GenerateAPIToken generates an API token.
func GenerateAccessToken ¶
func GenerateAccessToken(userName string, userID int, mode common.ReleaseMode, secret string, tokenDuration time.Duration) (string, error)
GenerateAccessToken generates an access token for web.
func GenerateMCPAccessToken ¶
func GenerateMCPAccessToken(userName string, userID int, clientID, audience, scope, secret string, tokenDuration time.Duration) (string, error)
GenerateMCPAccessToken mints an access token for the MCP resource server.
The audience is the MCP endpoint's canonical resource identifier (RFC 8707), which is the same string the protected-resource metadata advertises. Binding the token to that identifier means VerifyAccessToken keeps refusing it on the user API, and a token issued by another deployment never verifies here. scope is what the client was granted; the MCP endpoint requires the one scope its metadata advertises.
clientID names the registration the grant was issued to. It is carried in the token so Logout can retire that client's refresh tokens along with the access token it was handed.
func GenerateRefreshToken ¶
GenerateRefreshToken mints an opaque refresh token. The value is the credential itself; only its digest is ever stored.
func GenerateRestrictedAccessToken ¶
func GenerateRestrictedAccessToken(userName string, userID int, mode common.ReleaseMode, secret string, tokenDuration time.Duration, restriction TokenRestriction) (string, error)
GenerateRestrictedAccessToken generates an access token the auth interceptor accepts only for the RPCs allowed by restriction.
func GetRefreshTokenCookie ¶
func GetRefreshTokenCookie(ctx context.Context, stores *store.Store, token string, expiresAt time.Time) *http.Cookie
GetRefreshTokenCookie builds the web session's refresh cookie. Passing token="" clears it, which is what Logout sets.
The cookie is HttpOnly and Secure/SameSite follow the same server-side external-URL rule as the access-token cookie, so page JavaScript can never read the credential and a cross-site request cannot make the browser send it. expiresAt is the session's absolute expiry, not "now plus a duration": a rotated token inherits the original deadline, so the cookie must not outlive the session it stands for.
func GetRefreshTokenDuration ¶
GetRefreshTokenDuration is how long a newly issued refresh token stays usable.
func GetRefreshTokenFromCookie ¶
GetRefreshTokenFromCookie reads the refresh token a browser sent. A request with no such cookie returns an empty string; the caller decides whether that is a refusal.
func GetTokenCookie ¶
GetTokenCookie builds the web session cookie. Passing token="" clears it.
Secure and SameSite are derived from the server-side external URL, never from the client-controlled Origin header: a client could otherwise claim https and force SameSite=None, which is exactly the cross-site cookie behavior CSRF protection wants to avoid. Deployments that serve the SPA from another origin must configure an https external URL, which selects SameSite=None; Secure.
func GetTokenFromHeaders ¶
GetTokenFromHeaders extracts the access token from HTTP headers for ConnectRPC.
func HashToken ¶
HashToken returns the SHA-256 hex digest a refresh token is stored and looked up by. A digest is enough because the token is a high-entropy random string, not a password: there is nothing to brute force and no need for a slow KDF.
func IsAuthenticationAllowed ¶
func IsAuthenticationAllowed(_ string, authContext *common.AuthContext) bool
IsAuthenticationAllowed returns whether the method is exempted from authentication.
gRPC reflection is intentionally absent: its handlers are registered without the authentication interceptor and are therefore anonymous regardless of this function, and blanket-exempting the /grpc.reflection prefix here would be unreachable code.
func TokenPredatesPasswordChange ¶
TokenPredatesPasswordChange reports whether a credential issued at issuedAt must be rejected because the password changed at changedAt afterwards. A zero time is treated as "not applicable" so credentials without an issue time and users who never changed their password are unaffected. It is exported because the MCP refresh grant applies the same rule to the refresh token that Resolve applies to the access token it minted.
Types ¶
type APIAuthInterceptor ¶
type APIAuthInterceptor struct {
// contains filtered or unexported fields
}
APIAuthInterceptor is the auth interceptor for gRPC server.
func New ¶
func New( store *store.Store, secret string, stateCfg *state.State, profile *config.Profile, ) *APIAuthInterceptor
New returns a new API auth interceptor.
func (*APIAuthInterceptor) WrapStreamingClient ¶
func (*APIAuthInterceptor) WrapStreamingClient(next connect.StreamingClientFunc) connect.StreamingClientFunc
WrapStreamingClient implements the ConnectRPC interceptor interface for streaming clients.
func (*APIAuthInterceptor) WrapStreamingHandler ¶
func (in *APIAuthInterceptor) WrapStreamingHandler(next connect.StreamingHandlerFunc) connect.StreamingHandlerFunc
WrapStreamingHandler implements the ConnectRPC interceptor interface for streaming handlers.
type AccessTokenIdentity ¶
type AccessTokenIdentity struct {
UserID int
// TokenID is the jti claim, the handle Logout revokes the token by and the
// key of the persistent revocation record.
TokenID string
// ExpiresAt is the token's own expiry, so a resource server can hand it to
// its transport without parsing the token a second time.
ExpiresAt time.Time
IssuedAt time.Time
// Scopes are the scopes the token was minted with.
Scopes []string
// ClientID is the OAuth client an MCP token was issued to, empty for a web,
// CLI or API token. Logout uses it to retire the grants that client holds,
// which is what makes revoking one MCP token end the whole connection.
ClientID string
// Restriction is empty for a full-access token.
Restriction TokenRestriction
}
AccessTokenIdentity is the verified identity carried by an access token.
func VerifyAccessToken ¶
func VerifyAccessToken(accessTokenStr, secret string, mode common.ReleaseMode) (*AccessTokenIdentity, error)
VerifyAccessToken validates an access token's signature, algorithm, issuer, audience and expiry. It performs no database lookup, so callers that need the principal record must still load it.
func VerifyAccessTokenFor ¶
func VerifyAccessTokenFor(accessTokenStr, secret, audience string) (*AccessTokenIdentity, error)
VerifyAccessTokenFor is VerifyAccessToken with an explicit audience, for a resource server whose tokens carry an audience of their own (the MCP endpoint does). Audience checking stays inside the verifier on purpose: a caller that verified a signature and then forgot the audience comparison would accept a token minted for a different resource.
func VerifyAccessTokenProvenance ¶
func VerifyAccessTokenProvenance(accessTokenStr, secret string) (*AccessTokenIdentity, error)
VerifyAccessTokenProvenance validates that a token was issued by this server — signature, key id, issuer and expiry — without requiring a particular audience.
Logout needs it: an MCP token carries the endpoint's resource identifier as its audience rather than the user API's, and it must still be revocable, or a leaked MCP token has no self-service remedy at all. The signature is what stops a caller from flooding the revocation cache with forged strings and evicting genuine entries, so requiring an audience here would cost that protection nothing and buy the only way to revoke an MCP token.
type GatewayResponseModifier ¶
type GatewayResponseModifier struct{}
GatewayResponseModifier is the response modifier for grpc gateway.
func (*GatewayResponseModifier) Modify ¶
func (*GatewayResponseModifier) Modify(ctx context.Context, response http.ResponseWriter, _ proto.Message) error
Modify is the mux option for modifying response header.
type TokenAuthenticator ¶
type TokenAuthenticator struct {
// contains filtered or unexported fields
}
TokenAuthenticator turns a bearer token into the user it was issued to.
It exists so every entry point applies the same rules in the same order: signature, algorithm, issuer, audience and expiry, then the persistent revocation record (cached in front of the table), the principal lookup, deactivation, and the password-change cutoff. A second entry point that only verified the signature would keep accepting tokens for a user who has since been deactivated, had their password changed, or logged out.
func NewTokenAuthenticator ¶
func NewTokenAuthenticator(users UserStore, secret string, stateCfg *state.State) *TokenAuthenticator
NewTokenAuthenticator returns an authenticator backed by the given user store. stateCfg may be nil, in which case every revocation check reads the store; a nil users skips the check entirely (only tests do that).
func (*TokenAuthenticator) Resolve ¶
func (a *TokenAuthenticator) Resolve(ctx context.Context, accessTokenStr, audience string) (*store.UserMessage, *AccessTokenIdentity, error)
Resolve validates accessTokenStr for the given audience and returns the principal it belongs to. The returned error is one of the sentinel errors above, or a descriptive error naming the user the token belongs to.
type TokenRestriction ¶
type TokenRestriction string
TokenRestriction narrows what an access token may be used for. A restricted token is a fully valid, signed token; the auth interceptor refuses it on every RPC outside restrictedTokenAllowedProcedures.
const ( // TokenRestrictionResetPassword is issued when the workspace password policy // requires the user to rotate their password. It only allows the password // change itself and logging out. TokenRestrictionResetPassword TokenRestriction = "reset_password" )
type UserStore ¶
type UserStore interface {
GetUserByID(ctx context.Context, id int) (*store.UserMessage, error)
// IsTokenRevoked consults the persistent revocation records a Logout writes.
IsTokenRevoked(ctx context.Context, tokenID string) (bool, error)
}
UserStore is the part of the user store a TokenAuthenticator needs. *store.Store implements it; the interface exists so the token rules can be exercised without a database.