Skip to Content
ArchitectureWeb applicationSession, profile and access
Architecture · Web application

The session is the identity, and nothing else.

What it is for

Every page in NovusLens shows a different slice of an organisation depending on who is looking. The executive sees everything. A business-unit owner sees their unit and the units beneath it. The web application takes your identity from sign-in. A name in a link or request body cannot replace it.

The viewer comes from the signed session. The organisation check then returns the twin or a named refusal.

How it works

Signing in. The login page (app/login/page.tsx) runs a server action. getLoginRow reads the account from actor_map. verifyPassphrase in lib/session.ts checks the passphrase against the stored pbkdf2_sha256$iterations$salt$hash. An account with no stored hashHashA fixed-length fingerprint calculated from content. Comparing fingerprints helps detect changes and match records. can never sign in. A wrong passphrase and an account with no credential give the same refusal, so the form cannot be used to find out which accounts exist.

Throttling. Failed attempts are counted per account and per source address in process memory. Five failures inside ten minutes refuse further attempts with a different message (error=2).

With NOVUSLENS_TLS_PROXY=1, the source address is the final X-Forwarded-For entry. Allow only the trusted proxy to reach the web port. Configure it to replace forwarded headers with the client address and protocol it observes. With multiple proxy hops, the final proxy must supply the original client address.

Without a declared proxy, the source address comes from the connection. Browser-supplied address headers are ignored. A pinnedPinA recorded choice of one exact dependency version. It prevents an update from changing that dependency without a deliberate decision. Next.jsNext.jsThe web framework used to build and run the NovusLens interface. It provides pages, server code and request handlers. patch supplies that connection address to the server action. The regression tests check that a forged header cannot replace it.

The cookieCookieA small value a browser keeps and sends back to a website. NovusLens uses a signed cookie to remember the signed-in account.. A successful sign-in sets nl_session: httpOnly, sameSite=lax, path /. Its value is v1.<viewer>.<expiry>.<signature>, signed with HMAC-SHA256HMAC-SHA256A method that combines a secret key with content to make a checkable signature. The session cookie uses it to detect changes. (makeSessionValue). It lasts 12 hours. parseSessionValue checks signaturesDigital signatureA check calculated with a signing key that can detect whether a record has changed. Identifying the signer also requires a trusted link between that key and its owner. without revealing where their values differ through comparison timing and rejects an expired or malformed value.

Resolving the viewer. Every page calls resolveViewer (lib/session.ts). The rule is short:

  1. If there is a valid session, the session is the viewer.
  2. Otherwise, in the development profileDeployment profileA setting that selects customer or development behaviour. Customer mode refuses development credentials and uses the signed-in session as the identity. only, an explicit ?viewer= parameter is honoured.
  3. Otherwise there is no viewer, and the page refuses.

In the customer profile step 2 does not exist. There is no viewer override, and no default account either: devDefaultViewer returns the cxo-<org> convention in development and null in the customer profile.

The deployment profile

lib/profile.ts reads CONWAY_PROFILE. The value customer turns each development convenience into a refusal. The same switch is read by the Python services.

SettingCustomer profileDevelopment
?viewer= parameter or body viewerIgnoredHonoured when no session exists
CONWAY_SESSION_SECRETMust be set, at least 24 characters, not the public dev value, or signing throwsFalls back to a public dev secret and writes a WARNING line to the server log
CONWAY_SERVICE_TOKENSent as X-Conway-Service-Token on every call to the reasoning serviceSent only if set
Unauthenticated call to the proxyProxyA service that receives a request and forwards it to another service. The NovusLens web proxy adds the signed-in identity and service token.401Passed through with its own viewer

The reasoning service address comes from CONWAY_REASONING_URL, default http://127.0.0.1:8500.

lib/cookiePolicy.ts decides whether the cookie is marked encrypted-only. Browsers differ here: Safari discards an encrypted-only cookie set over plain HTTPHTTPHypertext Transfer Protocol: the rules a browser or program uses to send a request to a web service and receive its response., so the rule follows how the request arrived.

  • In the customer profile, a single forwarded protocol value of https makes the cookie encrypted-only.
  • With a declared TLSTLSTransport Layer Security: the technology that encrypts a connection and helps check the identity of the service at the other end. proxy (NOVUSLENS_TLS_PROXY=1), any other protocol value refuses sign-in before the page offers credentials.
  • The server action repeats that check before checking credentials (error=3).
  • Otherwise the interface is reachable from the host itself only, and a normal cookie is set.
  • The development profile always sets a normal cookie.

Organisation access

Pages that show an organisation load it through loadTwinForPage in lib/pageAccess.ts. It returns either the twinTwinNovusLens’s model of one organisation, built from records about its teams, services and how they rely on one another. It is only as current as those records. or one of three refusals, and components/AccessRefusal.tsx renders them:

RefusalMeaning
signed-outNo viewer. Offers a sign-in link.
not-authorisedThe account is not admitted to the organisation. It does not say whether the organisation exists.
forgottenThe organisation was erased. Only an admitted account learns this, with the date.

Admission uses orgAdmits in lib/orgScope.ts: an actor acts only inside their own organisation, except the operator role, which can access all organisations. orgAccessState in lib/twin.ts applies it, and the snapshot load repeats the check and throws ViewerRefusedError if it fails. A genuine outage (database or service down) is thrown instead and reaches app/error.tsx, the “can’t load your organisation” page, never a refusal.

Interfaces

RouteMethodBehaviour
/api/reasoning/[...path]POST, GETThe only way the browser reaches the reasoning service. Binds the identity (below).
/api/access/recordGETAnswers whether this reader may see the record a receiptReceiptA recorded acknowledgement of a request or action, with its identity, time and result. An evidence intake receipt confirms storage. It does not establish acceptance or prove that an agent acted. refers to: 204 yes, 401 signed out, 403 not now. Checks cell, finding and entry against the current twin. It returns no body, so it reveals no record contents.
/api/guide/[id]GETOne GuideGuideThe help pages inside NovusLens. They explain the pages, input formats and tasks in the version you use. They are product instructions, not evidence about the organisation. page for the help drawer. 401 when signed out, 404 when the release has no page for that section, 503 when the Guide is unavailable.

The reasoning proxy

For each request app/api/reasoning/[...path]/route.ts:

  1. Resolves the session viewer. With no session in the customer profile it returns 401.
  2. Overwrites the viewer: the viewer field of a JSONJSONJavaScript Object Notation: a text format for data, using named fields, lists and values. Configuration files and service messages use it. body, or the viewer query parameter, is replaced with the session identity. A body that is not a JSON object when a session exists gets 400.
  3. Adds the shared service tokenService tokenA secret value sent with requests between NovusLens services. The receiving service refuses requests with a missing or wrong value. and forwards the call to CONWAY_REASONING_URL. POST waits up to 300 seconds, because a local modelLanguage modelSoftware that produces text from instructions and supplied context. NovusLens checks its answers against the records retrieved for the question. may take time to produce answers. GET waits 30 seconds.
  4. If the service does not answer, returns 503 with a plain-words JSON message, never an HTMLHTMLHypertext Markup Language: the format used to describe a web page's content and structure. error page.

The role and scopeScopeThe work, data, activity or access that a rule, statement or calculation covers. For access, it names the records an account can see. For team measures, “Own operations” covers the selected unit. “Including descendants” adds the units beneath it. guards still live in the reasoning service’s tool handlers. The proxy adds the identity they need and removes nothing.

Whatever viewer a request names, the service sees the session identity.

Invariants and failure behaviour

  • A caller-supplied viewer never outranks a login, and cannot act at all in the customer profile.
  • A missing or weak session secret in the customer profile throws rather than signing forgeable cookies.
  • Throttling state is held in process memory, so it resets when the application restarts.
  • Single sign-onSSOSingle sign-on: signing in through an organisation's existing identity service instead of a separate product account. is described in the source as a later step that would sit in front of the same cookie. No single sign-on (SSO) code exists in apps/ui.
Last updated