Z3 expression translator fixes: - implies(x, y) now preserves both arguments (was dropping both → (=> true true)) - Multi-level field access m.price.amount → m_price_amount (was m_price.(>= amount 0)) - len() now handled same as length() (was left as bare len()) - nil/null translated to speckl_nil sentinel constant - != now properly converts to (distinct X Y) via convertToPrefix (was bare 'distinct') - Added 'distinct' as binary operator in convertToPrefix - speckl_nil declared as Int constant in output All 8 product spec constraints now produce valid SMT-LIB2. 57/57 tests pass.
192 lines
10 KiB
TypeScript
192 lines
10 KiB
TypeScript
// Auto-generated by speckl-compile from OAuth2AuthorizationCode
|
|
// DO NOT EDIT MANUALLY — regenerate from .speckdl source
|
|
|
|
export const OAuth2AuthorizationCodeOAuth2AuthorizationCodeState = {
|
|
Open: 'open',
|
|
Closed: 'closed',
|
|
} as const;
|
|
export type OAuth2AuthorizationCodeOAuth2AuthorizationCodeState = typeof OAuth2AuthorizationCodeOAuth2AuthorizationCodeState[keyof typeof OAuth2AuthorizationCodeOAuth2AuthorizationCodeState];
|
|
|
|
|
|
|
|
// Simple hash utility for SpeckDL hash() function
|
|
// In production, replace with a real crypto hash
|
|
function simpleHash(s: string, method?: string): string {
|
|
let h = 0;
|
|
for (let i = 0; i < s.length; i++) {
|
|
h = ((h << 5) - h + s.charCodeAt(i)) | 0;
|
|
}
|
|
return 'h_' + Math.abs(h).toString(16);
|
|
}
|
|
|
|
|
|
// Runtime type placeholders (opaque record types)
|
|
type AccessToken = any;
|
|
type AuthorizationCode = any;
|
|
type Bool }) = any;
|
|
type ClientId = any;
|
|
type ClientSecret = any;
|
|
type CodeChallenge = any;
|
|
type CodeVerifier = any;
|
|
type Map(AccessToken, { client_id: ClientId = any;
|
|
type Map(AuthorizationCode, { client_id: ClientId = any;
|
|
type Map(RefreshToken, { client_id: ClientId = any;
|
|
type Nat }) = any;
|
|
type RedirectUri = any;
|
|
type RefreshRequest = any;
|
|
type RefreshToken }) = any;
|
|
type Scope = any;
|
|
type SessionId = any;
|
|
type State = any;
|
|
type TokenRequest = any;
|
|
type UserId = any;
|
|
|
|
export class OAuth2AuthorizationCodeMachine {
|
|
clients!: Map<ClientId, { redirect_uris: Set<RedirectUri>; client_secret: ClientSecret }>;
|
|
authorizationCodes!: Map(AuthorizationCode, { client_id: ClientId;
|
|
redirect_uri!: RedirectUri;
|
|
state!: State;
|
|
code_challenge!: CodeChallenge;
|
|
code_challenge_method!: string;
|
|
scope!: Scope;
|
|
user_id!: UserId;
|
|
session_id!: SessionId;
|
|
created_at!: number;
|
|
used!: Bool });
|
|
accessTokens!: Map(AccessToken, { client_id: ClientId;
|
|
user_id!: UserId;
|
|
scope!: Scope;
|
|
expires_at!: number;
|
|
session_id!: SessionId;
|
|
refresh_token!: RefreshToken });
|
|
refreshTokens!: Map(RefreshToken, { client_id: ClientId;
|
|
user_id!: UserId;
|
|
scope!: Scope;
|
|
access_token!: AccessToken;
|
|
created_at!: Nat });
|
|
sessions!: Map<SessionId, { user_id: UserId }>;
|
|
stateNonces!: Map<State, { client_id: ClientId; redirect_uri: RedirectUri }>;
|
|
pkceChallenges!: Map<CodeChallenge, { code_verifier_hash: string; method: string }>;
|
|
currentTime!: number;
|
|
decisionLog: any[] = [];
|
|
|
|
constructor() {
|
|
// No init block defined
|
|
}
|
|
|
|
Authorize(client: ClientId, redirectUri: RedirectUri, scope: Scope, userId: UserId, sessionId: SessionId) {
|
|
const xxx: any = {} as any; // FIXME: parser dropped let/forall
|
|
const client_id: any = {} as any; // FIXME: parser dropped let/forall
|
|
const code_verifier_hash: any = {} as any; // FIXME: parser dropped let/forall
|
|
const __STR_LIT_0__: any = {} as any; // FIXME: parser dropped let/forall
|
|
const method: any = {} as any; // FIXME: parser dropped let/forall
|
|
const __STR_LIT_1__: any = {} as any; // FIXME: parser dropped let/forall
|
|
if (!(this.clients.has(client))) throw new Error('Guard failed: this.clients.has(client)');
|
|
if (!(this.clients.get(client)!.redirect_uris.has(redirectUri))) throw new Error('Guard failed: this.clients.get(client)!.redirect_uris.has(redirectUri)');
|
|
if (!(this.sessions.has(sessionId))) throw new Error('Guard failed: this.sessions.has(sessionId)');
|
|
if (!(this.sessions.get(sessionId)!.user_id === userId)) throw new Error('Guard failed: this.sessions.get(sessionId)!.user_id === userId');
|
|
const state = "state_" + simpleHash(client + redirectUri + this.currentTime);
|
|
const codeVerifier = "verifier_" + simpleHash(client + userId + this.currentTime);
|
|
const codeChallenge = simpleHash(codeVerifier, "S256");
|
|
this.stateNonces.set(state, { client_id: client, redirect_uri: redirectUri });
|
|
this.pkceChallenges.set(codeChallenge, { code_verifier_hash: simpleHash(codeVerifier, __STR_LIT_0__), method: __STR_LIT_1__ });
|
|
const authCode = "code_" + simpleHash(client + userId + this.currentTime);
|
|
this.authorizationCodes.set(authCode, { client_id: client, redirect_uri: redirectUri, state: state, code_challenge: codeChallenge, code_challenge_method: __STR_LIT_0__, scope: scope, user_id: userId, session_id: sessionId, created_at: this.currentTime, used: false });
|
|
}
|
|
|
|
ApproveAuthorization(code: AuthorizationCode, userId: UserId) {
|
|
const xxx: any = {} as any; // FIXME: parser dropped let/forall
|
|
if (!(this.authorizationCodes.has(code))) throw new Error('Guard failed: this.authorizationCodes.has(code)');
|
|
const c = this.authorizationCodes.get(code)!;
|
|
if (!(c.user_id === userId)) throw new Error('Guard failed: c.user_id === userId');
|
|
if (!(c.used === false)) throw new Error('Guard failed: c.used === false');
|
|
}
|
|
|
|
ExchangeCode(req: TokenRequest) {
|
|
const xxx: any = {} as any; // FIXME: parser dropped let/forall
|
|
const client_id: any = {} as any; // FIXME: parser dropped let/forall
|
|
if (!(this.clients.has(req.client_id))) throw new Error('Guard failed: this.clients.has(req.client_id)');
|
|
if (!(this.authorizationCodes.has(req.code))) throw new Error('Guard failed: this.authorizationCodes.has(req.code)');
|
|
const c = this.authorizationCodes.get(req.code)!;
|
|
if (!(c.used === false)) throw new Error('Guard failed: c.used === false');
|
|
if (!(c.client_id === req.client_id)) throw new Error('Guard failed: c.client_id === req.client_id');
|
|
if (!(c.redirect_uri === req.redirect_uri)) throw new Error('Guard failed: c.redirect_uri === req.redirect_uri');
|
|
const expectedChallenge = simpleHash(req.code_verifier, c.code_challenge_method);
|
|
if (!(expectedChallenge === c.code_challenge)) throw new Error('Guard failed: expectedChallenge === c.code_challenge');
|
|
this.pkceChallenges.delete(c.code_challenge);
|
|
this.stateNonces.delete(c.state);
|
|
const accessToken = "access_" + simpleHash(req.client_id + c.user_id + this.currentTime);
|
|
const refreshToken = "refresh_" + simpleHash(req.client_id + c.user_id + this.currentTime);
|
|
const expiresAt = this.currentTime + 3600;
|
|
this.accessTokens.set(accessToken, { client_id: req.client_id, user_id: c.user_id, scope: c.scope, expires_at: expiresAt, session_id: c.session_id, refresh_token: refreshToken });
|
|
this.refreshTokens.set(refreshToken, { client_id: req.client_id, user_id: c.user_id, scope: c.scope, access_token: accessToken, created_at: this.currentTime });
|
|
}
|
|
|
|
RefreshToken(req: RefreshRequest) {
|
|
const xxx: any = {} as any; // FIXME: parser dropped let/forall
|
|
const client_id: any = {} as any; // FIXME: parser dropped let/forall
|
|
if (!(this.clients.has(req.client_id))) throw new Error('Guard failed: this.clients.has(req.client_id)');
|
|
if (!(this.refreshTokens.has(req.refresh_token))) throw new Error('Guard failed: this.refreshTokens.has(req.refresh_token)');
|
|
const r = this.refreshTokens.get(req.refresh_token)!;
|
|
if (!(r.client_id === req.client_id)) throw new Error('Guard failed: r.client_id === req.client_id');
|
|
this.accessTokens.delete(r.access_token);
|
|
const newAccessToken = "access_" + simpleHash(req.client_id + r.user_id + this.currentTime);
|
|
const newRefreshToken = "refresh_" + simpleHash(req.client_id + r.user_id + this.currentTime);
|
|
const expiresAt = this.currentTime + 3600;
|
|
this.accessTokens.set(newAccessToken, { client_id: req.client_id, user_id: r.user_id, scope: req.scope.or(r.scope), expires_at: expiresAt, session_id: r.access_token.session_id, refresh_token: newRefreshToken });
|
|
this.refreshTokens.set(newRefreshToken, { client_id: req.client_id, user_id: r.user_id, scope: req.scope.or(r.scope), access_token: newAccessToken, created_at: this.currentTime });
|
|
this.refreshTokens.delete(req.refresh_token);
|
|
}
|
|
|
|
RevokeToken(client: ClientId, token: string, tokenType: string) {
|
|
const xxx: any = {} as any; // FIXME: parser dropped let/forall
|
|
const t: any = {} as any; // FIXME: parser dropped let/forall
|
|
const r: any = {} as any; // FIXME: parser dropped let/forall
|
|
if (!(this.clients.has(client))) throw new Error('Guard failed: this.clients.has(client)');
|
|
if (tokenType === "access") {
|
|
if (!(this.accessTokens.has(token))) throw new Error('Guard failed: this.accessTokens.has(token)');
|
|
this.t = this.accessTokens.get(token)!;
|
|
if (!(t.client_id === client)) throw new Error('Guard failed: t.client_id === client');
|
|
this.s[token] = null;
|
|
this.s[t.refresh_token] = null;
|
|
} else {
|
|
if (tokenType === "refresh") {
|
|
if (!(this.refreshTokens.has(token))) throw new Error('Guard failed: this.refreshTokens.has(token)');
|
|
this.r = this.refreshTokens.get(token)!;
|
|
if (!(r.client_id === client)) throw new Error('Guard failed: r.client_id === client');
|
|
this.s[token] = null;
|
|
this.s[r.access_token] = null;
|
|
}
|
|
}
|
|
}
|
|
|
|
VerifyPKCE(code: AuthorizationCode, verifier: CodeVerifier) {
|
|
const xxx: any = {} as any; // FIXME: parser dropped let/forall
|
|
if (!(this.authorizationCodes.has(code))) throw new Error('Guard failed: this.authorizationCodes.has(code)');
|
|
const c = this.authorizationCodes.get(code)!;
|
|
if (!(c.used === false)) throw new Error('Guard failed: c.used === false');
|
|
const expectedChallenge = simpleHash(verifier, c.code_challenge_method);
|
|
if (!(expectedChallenge === c.code_challenge)) throw new Error('Guard failed: expectedChallenge === c.code_challenge');
|
|
}
|
|
|
|
|
|
|
|
// V1: Always(NoTokenLeakage)
|
|
// Z3 call-out: Bounded Model Checking (BMC) with depth = decisionLog.length+1.
|
|
// Mirrors the SMT-LIB BMC emitted by compiler/src/generators/z3.ts (emitBMC).
|
|
// The inner expression is SpeckDL; we translate it to a TS boolean
|
|
// check that reads the current class state via this.<stateVar>.
|
|
async verify1(): Promise<boolean> {
|
|
// Temporal: Always(NoTokenLeakage)
|
|
// LTL: always(p). Bounded check — p must hold at every step in the
|
|
// bounded trace (decision log + current state). The first step
|
|
// where p is false violates the always-quantified property.
|
|
if (!(NoTokenLeakage)) return false;
|
|
for (const _evt of this.decisionLog) {
|
|
// Each emit() entry is a step boundary; p is re-evaluated
|
|
// against the live state (BMC "safety-at-horizon" view).
|
|
if (!(NoTokenLeakage)) return false;
|
|
}
|
|
return true;
|
|
}
|
|
}
|