<feed xmlns='http://www.w3.org/2005/Atom'>
<title>progmaweb/docs/ARCHITECTURE.md, branch main</title>
<subtitle>The site and account service of Progmasoft: a Next.js frontend and an ASP.NET Core API.</subtitle>
<link rel='alternate' type='text/html' href='https://git.progmasoft.com/progmaweb/'/>
<entry>
<title>Give every page a Content-Security-Policy with its own nonce</title>
<updated>2026-11-06T05:05:21+00:00</updated>
<author>
<name>Leitwolf11</name>
</author>
<published>2026-11-06T05:05:21+00:00</published>
<link rel='alternate' type='text/html' href='https://git.progmasoft.com/progmaweb/commit/?id=c4fb3effa7f806c570771bb83c0ed6988006bc24'/>
<id>c4fb3effa7f806c570771bb83c0ed6988006bc24</id>
<content type='text'>
The pages of progmasoft.com and its hosts had no content security
policy. The proxy now creates a 128-bit nonce for every request and sends
the policy both ways: on the request, so that Next marks its own inline
scripts with the nonce, and on the response. The layout reads the nonce
for the theme bootstrap. Every page is rendered on demand already, so
none is served with a stale nonce.

Scripts may come from the host, carry the nonce, or be Cloudflare's Web
Analytics beacon from static.cloudflareinsights.com, which reports to
cloudflareinsights.com. 'strict-dynamic' is left out, because browsers
ignore host sources under it and the beacon is added by Cloudflare
without a nonce. Styles allow inline ones; objects, framing and other
base URLs are refused. There is no form-action: sign-in and OAuth
consent end in redirects to other hosts, loopback addresses included,
and browsers apply form-action to them. The development server also
gets 'unsafe-eval' for its hot reload.

Tests check the nonce and each directive; ARCHITECTURE.md describes the
policy.
</content>
<content type='xhtml'>
<div xmlns='http://www.w3.org/1999/xhtml'>
<pre>
The pages of progmasoft.com and its hosts had no content security
policy. The proxy now creates a 128-bit nonce for every request and sends
the policy both ways: on the request, so that Next marks its own inline
scripts with the nonce, and on the response. The layout reads the nonce
for the theme bootstrap. Every page is rendered on demand already, so
none is served with a stale nonce.

Scripts may come from the host, carry the nonce, or be Cloudflare's Web
Analytics beacon from static.cloudflareinsights.com, which reports to
cloudflareinsights.com. 'strict-dynamic' is left out, because browsers
ignore host sources under it and the beacon is added by Cloudflare
without a nonce. Styles allow inline ones; objects, framing and other
base URLs are refused. There is no form-action: sign-in and OAuth
consent end in redirects to other hosts, loopback addresses included,
and browsers apply form-action to them. The development server also
gets 'unsafe-eval' for its hot reload.

Tests check the nonce and each directive; ARCHITECTURE.md describes the
policy.
</pre>
</div>
</content>
</entry>
<entry>
<title>Return a failed Google callback to sign-in and correct stale statements</title>
<updated>2026-10-16T03:00:57+00:00</updated>
<author>
<name>Leitwolf11</name>
</author>
<published>2026-10-16T03:00:57+00:00</published>
<link rel='alternate' type='text/html' href='https://git.progmasoft.com/progmaweb/commit/?id=bc653270652c1ab44d2144ddd17ae49b404e7cd2'/>
<id>bc653270652c1ab44d2144ddd17ae49b404e7cd2</id>
<content type='text'>
A Google callback that could not finish ended in an unhandled 500: a
visitor who declined on Google's consent screen, a callback whose state
cookie had expired, and a direct request to the callback path all got an
empty error page. The account API document already promised a redirect
to /login?google=failed for a failed callback; the handler of the Google
scheme now does that through OnRemoteFailure. Three new tests cover a
callback without state, a declined consent and a forged state.

Three statements no longer matched the sites:

- the bug report template still named the catalog paths without the
  language and the build system; they are /langs/tala/&lt;Publisher&gt;/&lt;Name&gt;/
  and /dslplugins/tazel/&lt;Publisher&gt;/&lt;Name&gt;/;
- the operations guide placed Tala content on xsharp-lang.xyz, which only
  redirects to tala.run now;
- the architecture guide described the ViGet routing before the catalogs
  per language and build system: it now gives the rewrites of /langs/tala
  and /dslplugins/tazel, the redirects of /langs and /dslplugins, three
  indexed ViGet pages and a sitemap with the home page and both catalogs.
</content>
<content type='xhtml'>
<div xmlns='http://www.w3.org/1999/xhtml'>
<pre>
A Google callback that could not finish ended in an unhandled 500: a
visitor who declined on Google's consent screen, a callback whose state
cookie had expired, and a direct request to the callback path all got an
empty error page. The account API document already promised a redirect
to /login?google=failed for a failed callback; the handler of the Google
scheme now does that through OnRemoteFailure. Three new tests cover a
callback without state, a declined consent and a forged state.

Three statements no longer matched the sites:

- the bug report template still named the catalog paths without the
  language and the build system; they are /langs/tala/&lt;Publisher&gt;/&lt;Name&gt;/
  and /dslplugins/tazel/&lt;Publisher&gt;/&lt;Name&gt;/;
- the operations guide placed Tala content on xsharp-lang.xyz, which only
  redirects to tala.run now;
- the architecture guide described the ViGet routing before the catalogs
  per language and build system: it now gives the rewrites of /langs/tala
  and /dslplugins/tazel, the redirects of /langs and /dslplugins, three
  indexed ViGet pages and a sitemap with the home page and both catalogs.
</pre>
</div>
</content>
</entry>
<entry>
<title>Keep accounts and sessions in PostgreSQL</title>
<updated>2026-10-06T19:53:46+00:00</updated>
<author>
<name>Leitwolf11</name>
</author>
<published>2026-10-06T19:53:46+00:00</published>
<link rel='alternate' type='text/html' href='https://git.progmasoft.com/progmaweb/commit/?id=1c986ff99b3c8f349e4a63c0e922276ddf7dc836'/>
<id>1c986ff99b3c8f349e4a63c0e922276ddf7dc836</id>
<content type='text'>
The account API kept everything in the memory of its process, so every
restart and every deployment of the API deleted all accounts and signed
everyone out.

PostgresAccountStore implements IAccountStore on PostgreSQL through Npgsql.
The three uniqueness rules are unique constraints, so two requests that race
for one name, address or Google identity are decided by the database. A
refused account is an ordinary answer, not an exception, and the rules are
reported in the order the in-memory store reports them. A session refers to
its account, and a session of an account that is not stored is refused; the
in-memory store now refuses it as well.

The schema is a list of numbered steps. The service takes the missing ones
when it starts, in one transaction under an advisory lock, and does not
start when the database cannot be reached or is newer than the release.
/health answers 503 while the database does not answer.

ConnectionStrings:Accounts selects PostgreSQL. A production service that is
given no database does not start; keeping accounts in memory there has to
be asked for with Accounts:Store=Memory. Development and the tests keep the
in-memory store by default.

The store gains RemoveExpiredSessionsAsync, and a background service calls
it at the start and once an hour: a session whose token is never presented
again no longer keeps its row for ever.

PostgreSQL keeps a timestamp to the microsecond. The store cuts a moment to
that precision and returns what it stored, and the service uses the
returned account, so the creation time in the registration response equals
the one every later read returns.

Tests. AccountStoreContractTests states the rules once and runs them on
both stores. Further tests cover the schema steps applied concurrently and a
database that is too new, the choice of the store from the configuration,
the removal of expired sessions, and an account with its session surviving
a restart of the service over HTTP. The PostgreSQL tests read their
database from PROGMAWEB_TEST_POSTGRES and work in a schema of their own
each. Without the variable they are skipped; CI starts PostgreSQL 16 for
them, and a test fails the run when the variable is missing there.

Operations. ops/README.md describes the database: the Unix socket with peer
authentication, so that no password exists, the one-time preparation, and
rollback across a schema step. The service unit starts after PostgreSQL.
docs/LIMITATIONS.md no longer lists the in-memory store and now lists the
missing backup and the missing deletion of accounts.
</content>
<content type='xhtml'>
<div xmlns='http://www.w3.org/1999/xhtml'>
<pre>
The account API kept everything in the memory of its process, so every
restart and every deployment of the API deleted all accounts and signed
everyone out.

PostgresAccountStore implements IAccountStore on PostgreSQL through Npgsql.
The three uniqueness rules are unique constraints, so two requests that race
for one name, address or Google identity are decided by the database. A
refused account is an ordinary answer, not an exception, and the rules are
reported in the order the in-memory store reports them. A session refers to
its account, and a session of an account that is not stored is refused; the
in-memory store now refuses it as well.

The schema is a list of numbered steps. The service takes the missing ones
when it starts, in one transaction under an advisory lock, and does not
start when the database cannot be reached or is newer than the release.
/health answers 503 while the database does not answer.

ConnectionStrings:Accounts selects PostgreSQL. A production service that is
given no database does not start; keeping accounts in memory there has to
be asked for with Accounts:Store=Memory. Development and the tests keep the
in-memory store by default.

The store gains RemoveExpiredSessionsAsync, and a background service calls
it at the start and once an hour: a session whose token is never presented
again no longer keeps its row for ever.

PostgreSQL keeps a timestamp to the microsecond. The store cuts a moment to
that precision and returns what it stored, and the service uses the
returned account, so the creation time in the registration response equals
the one every later read returns.

Tests. AccountStoreContractTests states the rules once and runs them on
both stores. Further tests cover the schema steps applied concurrently and a
database that is too new, the choice of the store from the configuration,
the removal of expired sessions, and an account with its session surviving
a restart of the service over HTTP. The PostgreSQL tests read their
database from PROGMAWEB_TEST_POSTGRES and work in a schema of their own
each. Without the variable they are skipped; CI starts PostgreSQL 16 for
them, and a test fails the run when the variable is missing there.

Operations. ops/README.md describes the database: the Unix socket with peer
authentication, so that no password exists, the one-time preparation, and
rollback across a schema step. The service unit starts after PostgreSQL.
docs/LIMITATIONS.md no longer lists the in-memory store and now lists the
missing backup and the missing deletion of accounts.
</pre>
</div>
</content>
</entry>
<entry>
<title>Document the architecture, the account API, localization and limitations</title>
<updated>2026-10-03T15:18:50+00:00</updated>
<author>
<name>Leitwolf11</name>
</author>
<published>2026-10-03T15:18:50+00:00</published>
<link rel='alternate' type='text/html' href='https://git.progmasoft.com/progmaweb/commit/?id=ba237e5ce9f50cd9e586697417c1d636e3cd129c'/>
<id>ba237e5ce9f50cd9e586697417c1d636e3cd129c</id>
<content type='text'>
The repository had a README and two operations notes. This adds the
documents a contributor or operator needs and corrects what had gone stale.

New, under docs/:
- README.md: index of the documentation.
- ARCHITECTURE.md: the two processes, the three hosts, the host-aware
  routing rules, the account flow and the trust boundaries.
- ACCOUNT-API.md: every endpoint with its request, responses and errors,
  the field rules, the Google flow outcome by outcome, cookies, limits and
  configuration. The responses were checked against a locally running API.
- LOCALIZATION.md: the four languages, how a page picks one, the cookie,
  how Hebrew text is laid out, and how to add a language.
- DEVELOPMENT.md: requirements, local setup, checks, tests, what each CI
  workflow enforces, the audit exception and the DocFX project.
- LIMITATIONS.md: what is missing or temporary, with the consequence of
  each: the in-memory account store, unverified addresses, no mail, no
  password change, the ignored returnTo parameter, the legacy ViGet status
  route, and that the API answers 500 to everything while the Google
  settings are empty.

Updated:
- README.md: languages, a documentation section, the local API command
  with its port, and the DocFX check.
- ops/README.md: restarting the API deletes every account, how to release
  a frontend-only change without that restart, and lessons from past
  releases.
- ops/mail/README.md: a status note that the API does not send mail yet.
- The pull request template names all four languages.

No source file changes.

Co-Authored-By: Claude Opus 5.5 &lt;noreply@anthropic.com&gt;
</content>
<content type='xhtml'>
<div xmlns='http://www.w3.org/1999/xhtml'>
<pre>
The repository had a README and two operations notes. This adds the
documents a contributor or operator needs and corrects what had gone stale.

New, under docs/:
- README.md: index of the documentation.
- ARCHITECTURE.md: the two processes, the three hosts, the host-aware
  routing rules, the account flow and the trust boundaries.
- ACCOUNT-API.md: every endpoint with its request, responses and errors,
  the field rules, the Google flow outcome by outcome, cookies, limits and
  configuration. The responses were checked against a locally running API.
- LOCALIZATION.md: the four languages, how a page picks one, the cookie,
  how Hebrew text is laid out, and how to add a language.
- DEVELOPMENT.md: requirements, local setup, checks, tests, what each CI
  workflow enforces, the audit exception and the DocFX project.
- LIMITATIONS.md: what is missing or temporary, with the consequence of
  each: the in-memory account store, unverified addresses, no mail, no
  password change, the ignored returnTo parameter, the legacy ViGet status
  route, and that the API answers 500 to everything while the Google
  settings are empty.

Updated:
- README.md: languages, a documentation section, the local API command
  with its port, and the DocFX check.
- ops/README.md: restarting the API deletes every account, how to release
  a frontend-only change without that restart, and lessons from past
  releases.
- ops/mail/README.md: a status note that the API does not send mail yet.
- The pull request template names all four languages.

No source file changes.

Co-Authored-By: Claude Opus 5.5 &lt;noreply@anthropic.com&gt;
</pre>
</div>
</content>
</entry>
</feed>
