agentfootprint
Version:
The explainable agent framework — backtrack a wrong answer to the exact context that caused it (evidence, not guesses). Built on footprintjs.
65 lines (64 loc) • 2.98 kB
TypeScript
/**
* directory/port — the four directory operations `directory-password` needs,
* as a port, so the rules run over a fake directory in tests and over LDAPS
* (`ldapDirectory`, through `ldapts`) in a deployment.
*/
/// <reference types="node" />
/// <reference types="node" />
import type { AdSubCodeName } from '../../../hosting/signin/audit.js';
/** One entry a search found. Binary attributes (objectGUID, objectSid) arrive as bytes. */
export interface DirectoryEntry {
readonly dn: string;
readonly objectGUID?: Buffer;
readonly displayName?: string;
}
/**
* A refused bind that says WHY: Active Directory's sub-code from the LDAP
* error (`… data 52e, v1db1` → `'52e'`). For the operator's audit record only —
* the person gets one answer whatever it says.
*/
export interface DirectoryBindRefused {
readonly kind: 'invalid';
/** AD's sub-code, lower-case hex, when the error carried one. */
readonly adSubCode?: string;
}
/** What a bind answered: `'ok'`, or a refusal — bare, or with AD's sub-code. */
export type DirectoryBind = 'ok' | 'invalid' | DirectoryBindRefused;
/** One connection, used for exactly one sign-in attempt, then closed. */
export interface DirectorySession {
/**
* A simple bind, sent ONCE — never retried. `'ok'` when the directory
* authenticated the name and password; `'invalid'` (or
* `{ kind: 'invalid', adSubCode }`) for ANY wrong credential. The sub-code
* reaches the audit record and never the person. Throws only when it could
* not ask.
*/
bind(name: string, password: string): Promise<DirectoryBind>;
/** RFC 4532 "Who am I?" — the authzId the directory says this connection is, e.g. `u:CORP\\alice`. Empty = anonymous. */
whoAmI(): Promise<string>;
/** A search below `base` (subtree) or AT `base` (`scope: 'base'`). */
search(base: string, filter: string, scope: 'sub' | 'base'): Promise<readonly DirectoryEntry[]>;
close(): Promise<void>;
}
/** Where connections come from. */
export interface Directory {
open(): Promise<DirectorySession>;
}
/**
* What an AD bind sub-code means: `52e` bad password, `775` locked, `532`
* password expired, `773` must change at next logon, `533` disabled, `701`
* account expired; anything else (`525` no such user, `530`/`531` logon
* hours/workstation, a code a later AD invents) is `'unknown'`.
*/
export declare function adSubCodeName(code: string): AdSubCodeName;
/**
* RFC 4515 §3: escape a value for an LDAP search filter. A string escapes `*`,
* `(`, `)`, `\` and NUL (and every non-ASCII byte, so nothing depends on how a
* server reads UTF-8 in a filter); bytes are escaped whole.
*/
export declare function escapeFilterValue(value: string | Buffer): string;
/**
* A SID in SDDL string form (`S-1-5-21-…-1104`) as the binary `objectSid` a
* filter compares, or `undefined` when it is not one.
*/
export declare function sidToBytes(sid: string): Buffer | undefined;