client/session.aug
OpenID Connect login application · Source and specification
Files in this project
main.augclient/contracts.augclient/endpoints.augclient/export.augclient/login.augclient/logout.augclient/protocol.augclient/session.augclient/views.augcommon/export.augcommon/headers.augcommon/keys.augcommon/settings.augcommon/views.augprovider/authorization.augprovider/contracts.augprovider/credentials.augprovider/discovery.augprovider/export.augprovider/token.augprovider/userinfo.augprovider/views.aug
Code
// aug-spec: "session.aug.md" explains this file. Read it before changes; refresh with aug spec.
import SessionClaims and SessionError from contracts
import settings and SigningKeys and KeyError from common
import Crypto and verifyJwt and JwtError from crypto
import Clock from time
import ExpiringStore from memory
/** An app session has its own key, issuer, audience and token type. A live registry entry is required so logout revokes a signed token immediately. */
authenticate(optional string token, resolve Crypto crypto, resolve Clock clock, resolve SigningKeys keys, resolve ExpiringStore<SessionClaims> sessions):
match token:
when null:
throw SessionError()
when some value:
try:
publicKey = crypto.publicRsa(key=keys.session())
claims = verifyJwt(
token=value,
publicKey,
kid="session-1",
tokenType="august-session+jwt"
).decode<SessionClaims>()
config = settings()
now = clock.now()
if claims.iss != config.baseUrl + "/app" or claims.aud != "august-app" or claims.sub.length() == 0 or claims.exp <= now or claims.iat > now + 30 or claims.iat < now - config.sessionSeconds or claims.exp <= claims.iat or claims.exp > now + config.sessionSeconds + 30:
throw SessionError()
if not claims.jti.isToken(min=43, max=43) or not claims.csrf.isToken(min=43, max=43):
throw SessionError()
match sessions.get(key=claims.jti, now=now):
when null:
throw SessionError()
when some saved:
if saved.sub != claims.sub or saved.exp != claims.exp or not crypto.equal(
left=saved.csrf.bytes(),
right=claims.csrf.bytes()
):
throw SessionError()
return claims
catch CryptoError error:
throw SessionError()
catch JwtError error:
throw SessionError()
catch JsonError error:
throw SessionError()// aug-spec: "session.aug.md" explains this file. Read it before changes; refresh with aug spec.
import SessionClaims and SessionError from contracts
import settings and SigningKeys and KeyError from common
import Crypto and verifyJwt and JwtError from crypto
import Clock from time
import ExpiringStore from memory
/** An app session has its own key, issuer, audience and token type. A live registry entry is required so logout revokes a signed token immediately. */
authenticate(optional string token, resolve Crypto crypto, resolve Clock clock, resolve SigningKeys keys, resolve ExpiringStore<SessionClaims> sessions) {
match token {
when null {
throw SessionError()
}
when some value {
try {
publicKey = crypto.publicRsa(key=keys.session())
claims = verifyJwt(
token=value,
publicKey,
kid="session-1",
tokenType="august-session+jwt"
).decode<SessionClaims>()
config = settings()
now = clock.now()
if claims.iss != config.baseUrl + "/app" or claims.aud != "august-app" or claims.sub.length() == 0 or claims.exp <= now or claims.iat > now + 30 or claims.iat < now - config.sessionSeconds or claims.exp <= claims.iat or claims.exp > now + config.sessionSeconds + 30 {
throw SessionError()
}
if not claims.jti.isToken(min=43, max=43) or not claims.csrf.isToken(min=43, max=43) {
throw SessionError()
}
match sessions.get(key=claims.jti, now=now) {
when null {
throw SessionError()
}
when some saved {
if saved.sub != claims.sub or saved.exp != claims.exp or not crypto.equal(
left=saved.csrf.bytes(),
right=claims.csrf.bytes()
) {
throw SessionError()
}
return claims
}
}
}
catch CryptoError error {
throw SessionError()
}
catch JwtError error {
throw SessionError()
}
catch JsonError error {
throw SessionError()
}
}
}
}Compiled specification
authenticate · source
An app session has its own key, issuer, audience and token type. A live registry entry is required so logout revokes a signed token immediately.
It takes token as optional string. It gets crypto (Crypto), clock (Clock), keys (SigningKeys), and sessions (ExpiringStore<SessionClaims>) from dependency injection. Omitted optional inputs are null.
It can call SigningKeys.session, Crypto.publicRsa, Clock.now, ExpiringStore<SessionClaims>.get, Crypto.equal, Crypto.decodeBase64url, and Crypto.verifyRsa. Failures can raise KeyError, SessionError, and TimeError.
If token is null, it raises a SessionError. The non-null token becomes value. It sets publicKey to crypto.publicRsa with key from keys.session. It sets claims to decode on verifyJwt with token from value, publicKey, kid "session-1", and tokenType "august-session+jwt" using injected crypto for SessionClaims. source
It gets config from settings. It sets now to clock.now. It checks that claims.iss equals the text {config.baseUrl}/app and claims.aud equals "august-app" and the byte length of claims.sub does not equal 0 and claims.exp is greater than now and claims.iat is at most (now plus 30) and claims.iat is at least (now minus config.sessionSeconds) and claims.exp is greater than claims.iat and claims.exp is at most ((now plus config.sessionSeconds) plus 30). It raises a SessionError at the first failed check. source
It checks that claims.jti is a URL-safe ASCII token with 43 to 43 characters and claims.csrf is a URL-safe ASCII token with 43 to 43 characters. It raises a SessionError at the first failed check. It obtains sessions.get with key from claims.jti and now. If no value is found, it raises a SessionError. source
The non-null result becomes saved. It checks that saved.sub equals claims.sub and saved.exp equals claims.exp and crypto.equal with left from the UTF-8 bytes of saved.csrf and right from the UTF-8 bytes of claims.csrf returns true. It raises a SessionError at the first failed check. It returns claims. source
If this work raises CryptoError, it raises a SessionError. If this work raises JwtError, it raises a SessionError. If this work raises JsonError, it raises a SessionError. source
Checked interface
authenticate(optional string token, resolve Crypto crypto, resolve Clock clock, resolve SigningKeys keys, resolve ExpiringStore<SessionClaims> sessions) returns SessionClaims unless KeyError and SessionError and TimeError uses SigningKeys.session, Crypto.publicRsa, Clock.now, ExpiringStore<SessionClaims>.get, Crypto.equal, Crypto.decodeBase64url, Crypto.verifyRsaIt takes token as optional string. It gets crypto (Crypto), clock (Clock), keys (SigningKeys), and sessions (ExpiringStore<SessionClaims>) from dependency injection. Omitted optional inputs are null.
It can call SigningKeys.session, Crypto.publicRsa, Clock.now, ExpiringStore<SessionClaims>.get, Crypto.equal, Crypto.decodeBase64url, and Crypto.verifyRsa. Failures can raise KeyError, SessionError, and TimeError.
Dependencies
It uses ExpiringStore (get) from memory. It uses Crypto (decodeBase64url, equal, publicRsa, and verifyRsa), JwtError, and verifyJwt from crypto. It uses Clock (now) from time. It uses SessionClaims (aud, csrf, exp, iat, iss, jti, and sub) and SessionError from contracts.
It uses KeyError, SigningKeys (session), and settings from common. It uses Settings (baseUrl and sessionSeconds).
Built-in operations follow the language reference.