<feed xmlns='http://www.w3.org/2005/Atom'>
<title>progmaweb/docs, branch security/next-16.4.2</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>Log why a Google callback could not finish</title>
<updated>2026-10-16T13:13:45+00:00</updated>
<author>
<name>Leitwolf11</name>
</author>
<published>2026-10-16T13:13:45+00:00</published>
<link rel='alternate' type='text/html' href='https://git.progmasoft.com/progmaweb/commit/?id=031cbf7efc5d97b28259da5b387cb3a3dbd242d5'/>
<id>031cbf7efc5d97b28259da5b387cb3a3dbd242d5</id>
<content type='text'>
A failed Google callback has returned the visitor to /login?google=failed
since the previous release, but nothing recorded why: a declined consent,
an expired or missing state cookie and a forged request all looked the
same, and the log of the service held no entry at all.

GoogleSignInFailure now handles OnRemoteFailure. It writes one warning,
event GoogleSignInFailed (id 5), with the cause: the error code Google
reported, such as access_denied, or the reason the handler refused the
callback, such as "The oauth state was missing or invalid." An error code
is written only when it is a short run of letters, digits, '_', '-' and
'.'; anything else from the query string is written as an unrecognized
error. The authorization code, the state and tokens are never logged.
The redirect is unchanged.

Four new tests cover the entry, the redirect, an unusual error value and
a refused state. The account API document describes the log entry.
</content>
<content type='xhtml'>
<div xmlns='http://www.w3.org/1999/xhtml'>
<pre>
A failed Google callback has returned the visitor to /login?google=failed
since the previous release, but nothing recorded why: a declined consent,
an expired or missing state cookie and a forged request all looked the
same, and the log of the service held no entry at all.

GoogleSignInFailure now handles OnRemoteFailure. It writes one warning,
event GoogleSignInFailed (id 5), with the cause: the error code Google
reported, such as access_denied, or the reason the handler refused the
callback, such as "The oauth state was missing or invalid." An error code
is written only when it is a short run of letters, digits, '_', '-' and
'.'; anything else from the query string is written as an unrecognized
error. The authorization code, the state and tokens are never logged.
The redirect is unchanged.

Four new tests cover the entry, the redirect, an unusual error value and
a refused state. The account API document describes the log entry.
</pre>
</div>
</content>
</entry>
<entry>
<title>Merge remote-tracking branch 'origin/main' into feature/thirty-day-sessions</title>
<updated>2026-10-16T04:16:14+00:00</updated>
<author>
<name>Leitwolf11</name>
</author>
<published>2026-10-16T04:16:14+00:00</published>
<link rel='alternate' type='text/html' href='https://git.progmasoft.com/progmaweb/commit/?id=235874b4e2bbf75acf2fd09ec37c3dfd480c01b7'/>
<id>235874b4e2bbf75acf2fd09ec37c3dfd480c01b7</id>
<content type='text'>
</content>
<content type='xhtml'>
<div xmlns='http://www.w3.org/1999/xhtml'>
<pre>
</pre>
</div>
</content>
</entry>
<entry>
<title>Keep account sessions for 30 days</title>
<updated>2026-10-16T03:46:23+00:00</updated>
<author>
<name>Leitwolf11</name>
</author>
<published>2026-10-16T03:46:23+00:00</published>
<link rel='alternate' type='text/html' href='https://git.progmasoft.com/progmaweb/commit/?id=205d91185e22b9129c528296cf07d15225d82866'/>
<id>205d91185e22b9129c528296cf07d15225d82866</id>
<content type='text'>
A session of a Progmasoft Account, whether opened with a password or with
Google, lasted 12 hours from the sign-in, and the setting could not raise
it beyond 7 days. Sessions now last 30 days by default, and
AccountSessions:LifetimeHours may set anything from 1 hour up to that new
maximum of 720 hours; a value outside the range is clamped to it, as
before.

The lifetime stays fixed from the sign-in; it is not extended by use. The
production environment file sets no lifetime, so the new default applies
there once the API is deployed. Sessions opened before keep the expiry
they were given.

New tests check the 30-day default and the clamping of 0, 12, 720 and
10 000 hours. The account API document gives the new default and range.
</content>
<content type='xhtml'>
<div xmlns='http://www.w3.org/1999/xhtml'>
<pre>
A session of a Progmasoft Account, whether opened with a password or with
Google, lasted 12 hours from the sign-in, and the setting could not raise
it beyond 7 days. Sessions now last 30 days by default, and
AccountSessions:LifetimeHours may set anything from 1 hour up to that new
maximum of 720 hours; a value outside the range is clamped to it, as
before.

The lifetime stays fixed from the sign-in; it is not extended by use. The
production environment file sets no lifetime, so the new default applies
there once the API is deployed. Sessions opened before keep the expiry
they were given.

New tests check the 30-day default and the clamping of 0, 12, 720 and
10 000 hours. The account API document gives the new default and range.
</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>Call the language Tala on the Progmasoft and ViGet pages</title>
<updated>2026-10-12T03:19:10+00:00</updated>
<author>
<name>Leitwolf11</name>
</author>
<published>2026-10-12T03:19:10+00:00</published>
<link rel='alternate' type='text/html' href='https://git.progmasoft.com/progmaweb/commit/?id=b71e93b547e9f6425cc83b121f01e7d82b88079f'/>
<id>b71e93b547e9f6425cc83b121f01e7d82b88079f</id>
<content type='text'>
The language Visual X# is now Tala. The Progmasoft home page, the ViGet
pages, the dashboard and the two footers name it so in the four languages of
the site; the project file is BUILD.kts, and the source link leads to
Progmasoft/tala. The README, the operations and localization documents and
the issue templates follow.

The localization document said that a left-to-right mark follows the name of
the language in Hebrew text. That was for the `#` of the old name; the
sentence now speaks of names that end in a symbol.

The links to xsharp-lang.xyz stay: the language site is still served there.
</content>
<content type='xhtml'>
<div xmlns='http://www.w3.org/1999/xhtml'>
<pre>
The language Visual X# is now Tala. The Progmasoft home page, the ViGet
pages, the dashboard and the two footers name it so in the four languages of
the site; the project file is BUILD.kts, and the source link leads to
Progmasoft/tala. The README, the operations and localization documents and
the issue templates follow.

The localization document said that a left-to-right mark follows the name of
the language in Hebrew text. That was for the `#` of the old name; the
sentence now speaks of names that end in a symbol.

The links to xsharp-lang.xyz stay: the language site is still served there.
</pre>
</div>
</content>
</entry>
<entry>
<title>Hand version updates to Renovate</title>
<updated>2026-10-10T19:30:30+00:00</updated>
<author>
<name>Leitwolf11</name>
</author>
<published>2026-10-10T19:30:30+00:00</published>
<link rel='alternate' type='text/html' href='https://git.progmasoft.com/progmaweb/commit/?id=7d874044576d6a10c3a6cbec44e70231ddfbada5'/>
<id>7d874044576d6a10c3a6cbec44e70231ddfbada5</id>
<content type='text'>
Renovate proposes the version updates of this repository from now on.
It watches what Dependabot watched, looks once a week, takes a release
a week after it is published, opens one pull request for each kind of
dependency with at most two open, and rebases a pull request only when
it conflicts. A dashboard issue lists what is pending.

The Dependabot configuration is removed, which turns its version
updates off. Dependabot keeps its alerts and its security updates,
which are repository settings. Renovate does not open its own pull
requests for vulnerabilities, so one advisory does not get two fixes.

The configuration passes Renovate's validator and takes effect when
the Renovate app is installed on the repository.
</content>
<content type='xhtml'>
<div xmlns='http://www.w3.org/1999/xhtml'>
<pre>
Renovate proposes the version updates of this repository from now on.
It watches what Dependabot watched, looks once a week, takes a release
a week after it is published, opens one pull request for each kind of
dependency with at most two open, and rebases a pull request only when
it conflicts. A dashboard issue lists what is pending.

The Dependabot configuration is removed, which turns its version
updates off. Dependabot keeps its alerts and its security updates,
which are repository settings. Renovate does not open its own pull
requests for vulnerabilities, so one advisory does not get two fixes.

The configuration passes Renovate's validator and takes effect when
the Renovate app is installed on the repository.
</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>Merge remote-tracking branch 'origin/main' into fix/api-without-google</title>
<updated>2026-10-03T15:46:59+00:00</updated>
<author>
<name>Leitwolf11</name>
</author>
<published>2026-10-03T15:46:59+00:00</published>
<link rel='alternate' type='text/html' href='https://git.progmasoft.com/progmaweb/commit/?id=b84a2216c26cc5392d0dc300b7a605ccb288d8c8'/>
<id>b84a2216c26cc5392d0dc300b7a605ccb288d8c8</id>
<content type='text'>
</content>
<content type='xhtml'>
<div xmlns='http://www.w3.org/1999/xhtml'>
<pre>
</pre>
</div>
</content>
</entry>
<entry>
<title>Run the account API without a Google client</title>
<updated>2026-10-03T15:46:35+00:00</updated>
<author>
<name>Leitwolf11</name>
</author>
<published>2026-10-03T15:46:35+00:00</published>
<link rel='alternate' type='text/html' href='https://git.progmasoft.com/progmaweb/commit/?id=ec774db15976385a6d5ea6f48d3622fe507d3843'/>
<id>ec774db15976385a6d5ea6f48d3622fe507d3843</id>
<content type='text'>
With an empty Google client identifier or secret the API answered every
request with 500, including registration, password sign-in and the status
route. The Google scheme was always registered, and the authentication
middleware initializes every remote scheme for each request; with empty
credentials the validation of the Google options threw there, before any
endpoint ran. The API could not be started locally without credentials, and
the "Google sign-in is unavailable" answer of the start endpoint was never
reached.

The Google scheme is now registered only when both values are configured.
Without it the service runs normally, the start endpoint answers 503, the
completion endpoint redirects to the failed sign-in page without a session,
and the callback path is not a route. With both values nothing changes.

The test factory passes the two values as host settings. The program makes
the decision while it registers services, and configuration sources a test
factory adds are applied only when the host is built, after that point.

New tests run the service without a Google client: the ordinary routes,
a full register, sign-out and sign-in round trip, and the three Google
paths. They failed with 500 against the previous program and pass now, also
when the environment of the test run carries Google credentials.

The guides no longer list this as a limitation or ask for placeholder
credentials.

Deploying this change restarts the API, which deletes every account while
the store is in memory.
</content>
<content type='xhtml'>
<div xmlns='http://www.w3.org/1999/xhtml'>
<pre>
With an empty Google client identifier or secret the API answered every
request with 500, including registration, password sign-in and the status
route. The Google scheme was always registered, and the authentication
middleware initializes every remote scheme for each request; with empty
credentials the validation of the Google options threw there, before any
endpoint ran. The API could not be started locally without credentials, and
the "Google sign-in is unavailable" answer of the start endpoint was never
reached.

The Google scheme is now registered only when both values are configured.
Without it the service runs normally, the start endpoint answers 503, the
completion endpoint redirects to the failed sign-in page without a session,
and the callback path is not a route. With both values nothing changes.

The test factory passes the two values as host settings. The program makes
the decision while it registers services, and configuration sources a test
factory adds are applied only when the host is built, after that point.

New tests run the service without a Google client: the ordinary routes,
a full register, sign-out and sign-in round trip, and the three Google
paths. They failed with 500 against the previous program and pass now, also
when the environment of the test run carries Google credentials.

The guides no longer list this as a limitation or ask for placeholder
credentials.

Deploying this change restarts the API, which deletes every account while
the store is in memory.
</pre>
</div>
</content>
</entry>
<entry>
<title>Describe the stricter documentation gate in the guides</title>
<updated>2026-10-03T15:42:57+00:00</updated>
<author>
<name>Leitwolf11</name>
</author>
<published>2026-10-03T15:42:57+00:00</published>
<link rel='alternate' type='text/html' href='https://git.progmasoft.com/progmaweb/commit/?id=08a076a3f880764462410a0b2f855d5bdfba1066'/>
<id>08a076a3f880764462410a0b2f855d5bdfba1066</id>
<content type='text'>
The guides merged from main still said that the DocFX gate does not require
documentation comments. With this branch it does, so:

- docs/DEVELOPMENT.md and README.md list the coverage check next to the
  DocFX command and say what fails the gate.
- docs/LIMITATIONS.md no longer lists missing documentation comments as a
  limitation.

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 guides merged from main still said that the DocFX gate does not require
documentation comments. With this branch it does, so:

- docs/DEVELOPMENT.md and README.md list the coverage check next to the
  DocFX command and say what fails the gate.
- docs/LIMITATIONS.md no longer lists missing documentation comments as a
  limitation.

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