Airlock

 view release on metacpan or  search on metacpan

.gitignore  view on Meta::CPAN

/Makefile
/Makefile.old
/MANIFEST*
!MANIFEST.SKIP
/META.*
/MYMETA.*
/resume
/AGENTS.md

# Claude Code — commit: skills/, agents/, hooks/, settings.json
# Ignore: local overrides, credentials, session data
.claude/*.local.*
.claude/local/
.claude/.credentials.json
.claude/statsig/
.claude/todos/
.claude/projects/

# karr materialized task view -- never commit
tasks/
config.yml
.karr

# skilletor: committe Config + Ignore-Block

docs/superpowers/plans/2026-10-02-airlock-phase-1.md  view on Meta::CPAN

    }
  ],
  "users": [
    {
      "username": "plain",
      "enabled": true,
      "email": "plain@example.org",
      "emailVerified": true,
      "firstName": "Plain",
      "lastName": "User",
      "credentials": [
        { "type": "password", "value": "plain-password", "temporary": false }
      ]
    },
    {
      "username": "otp",
      "enabled": true,
      "email": "otp@example.org",
      "emailVerified": true,
      "firstName": "Otp",
      "lastName": "User",
      "credentials": [
        { "type": "password", "value": "otp-password", "temporary": false },
        {
          "type": "otp",
          "secretData": "{\"value\":\"12345678901234567890\"}",
          "credentialData": "{\"subType\":\"totp\",\"digits\":6,\"counter\":0,\"period\":30,\"algorithm\":\"HmacSHA1\"}"
        }
      ]
    }
  ]
}

lib/Airlock/Upstream/Authentik.pm  view on Meta::CPAN


=item *

C<amr> and C<auth_time> are the same in the ID token and the access token.
(Introspection would say the same, but it needs a confidential client; the
public one the test uses gets C<< { active: false } >> and nothing else.)

=back

Two qualifications on C<auth_time>, both read in authentik's source rather
than provoked here. A client-credentials or token-exchange grant sets it to
the minting time, not to a login — harmless for this factor, which refuses
anything without an C<amr>, but it is not the session's time there. And when
authentik finds no login event for a session, it falls back to the current
time, so an old authentication can look new: that direction B<fails open>, and
C<max_age> cannot catch it.

=head2 Asking for a fresh authentication

L<Airlock::Factor::Upstream/reauth_params> returns C<< max_age => 0 >>, which
is what OpenID Connect says for "authenticate this person again". B<authentik

t/authentik/README.md  view on Meta::CPAN

  defaults. This is the difference to Keycloak, which says nothing until
  `t/keycloak/setup.pl` has run.
- **`acr` is one constant string** whatever happened, so `mfa_acr` stays empty
  in `Airlock::Upstream::Authentik`. Sending `acr_values` changes nothing.
- **`auth_time` is when the session began** for the grants Airlock meets — the
  authorization code and the device code both take it from the session's login
  event — and it survives a refresh and further authorization requests.
  `max_age` on the factor therefore measures the age of the authentication,
  which is what a caller wants: a token minted now from a ten-minute-old
  session is refused by `max_age => 300`. Two things it is not: for a
  client-credentials or token-exchange grant `auth_time` is the minting time
  (those carry no `amr`, so the factor refuses them anyway), and when authentik
  finds no login event for a session it falls back to the current time, which
  makes an old authentication look new. That last one fails open; it was read
  in authentik's source, not provoked here.
- **`max_age=0` is the one value authentik throws away**, because its check is
  on the truth of the number and zero is not true. Every other value works, and
  so does `prompt=login`. Measured against a session six seconds old:

  | sent | result |
  |---|---|

t/keycloak/realm.json  view on Meta::CPAN

    }
  ],
  "users": [
    {
      "username": "plain",
      "enabled": true,
      "email": "plain@example.org",
      "emailVerified": true,
      "firstName": "Plain",
      "lastName": "User",
      "credentials": [
        {
          "type": "password",
          "value": "plain-password",
          "temporary": false
        }
      ]
    },
    {
      "username": "otp",
      "enabled": true,
      "email": "otp@example.org",
      "emailVerified": true,
      "firstName": "Otp",
      "lastName": "User",
      "credentials": [
        {
          "type": "password",
          "value": "otp-password",
          "temporary": false
        },
        {
          "type": "otp",
          "secretData": "{\"value\":\"12345678901234567890\"}",
          "credentialData": "{\"subType\":\"totp\",\"digits\":6,\"counter\":0,\"period\":30,\"algorithm\":\"HmacSHA1\"}"
        }



( run in 3.897 seconds using v1.01-cache-2.11-cpan-3f962061493 )