diff --git a/.github/workflows/deploy_docs_4x.yml b/.github/workflows/deploy_docs_4x.yml index 07a43d25..c0d1ffb7 100644 --- a/.github/workflows/deploy_docs_4x.yml +++ b/.github/workflows/deploy_docs_4x.yml @@ -21,3 +21,4 @@ jobs: with: git_remote_url: 'ssh://dokku@apps.cakephp.org:22/authentication-docs-4' ssh_private_key: ${{ secrets.DOKKU_SSH_PRIVATE_KEY }} + branch: '4.x' diff --git a/Dockerfile b/Dockerfile index 7acfb27e..832f6bc1 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,26 +1,36 @@ -# Basic docker based environment -# Necessary to trick dokku into building the documentation -# using dockerfile instead of herokuish -FROM ubuntu:22.04 - -# Add basic tools -RUN apt-get update && \ - apt-get install -y build-essential \ - software-properties-common \ - curl \ - git \ - libxml2 \ - libffi-dev \ - libssl-dev - -# Prevent interactive timezone input -ENV DEBIAN_FRONTEND=noninteractive -RUN LC_ALL=C.UTF-8 add-apt-repository ppa:ondrej/php && \ - apt-get update && \ - apt-get install -y php8.1-cli php8.1-mbstring php8.1-xml php8.1-zip php8.1-intl php8.1-opcache php8.1-sqlite - -WORKDIR /code - -VOLUME ["/code"] - -CMD [ '/bin/bash' ] +# ---------------------- +# 1. Build stage +# ---------------------- +FROM node:22-alpine AS builder + +# Git is required because docs/package.json pulls a dependency from GitHub. +RUN apk add --no-cache git openssh-client + +WORKDIR /app/docs + +# Copy dependency manifests first to preserve Docker layer caching. +COPY docs/ ./ +RUN npm ci + +# Increase max-old-space-size to avoid memory issues during build +ENV NODE_OPTIONS="--max-old-space-size=8192" + +# Build the site. +RUN npm run docs:build + +# ---------------------- +# 2. Runtime stage (nginx) +# ---------------------- +FROM nginx:1.27-alpine AS runner + +# Copy built files +COPY --from=builder /app/docs/.vitepress/dist /usr/share/nginx/html + +# Expose port +EXPOSE 80 + +# Health check (optional) +HEALTHCHECK CMD wget --quiet --tries=1 --spider http://localhost:80/ || exit 1 + +# Start nginx +CMD ["nginx", "-g", "daemon off;"] diff --git a/docs.Dockerfile b/docs.Dockerfile deleted file mode 100644 index be650d6d..00000000 --- a/docs.Dockerfile +++ /dev/null @@ -1,25 +0,0 @@ -# Generate the HTML output. -FROM markstory/cakephp-docs-builder as builder - -COPY docs /data/docs -ENV LANGS="en es fr ja" - -# Build the docs with sphinx -RUN cd /data/docs-builder && \ - make website LANGS="$LANGS" SOURCE=/data/docs DEST=/data/website - -# Build a small nginx container with just the static site in it. -FROM markstory/cakephp-docs-builder:runtime as runtime - -# Configure search index script. -ENV LANGS="en es fr ja" -ENV SEARCH_SOURCE="/usr/share/nginx/html" -ENV SEARCH_URL_PREFIX="/authentication/4" - -COPY --from=builder /data/docs /data/docs -COPY --from=builder /data/website /data/website -COPY --from=builder /data/docs-builder/nginx.conf /etc/nginx/conf.d/default.conf - -# Move files into final location -RUN cp -R /data/website/html/* /usr/share/nginx/html \ - && rm -rf /data/website/ diff --git a/docs/.gitignore b/docs/.gitignore new file mode 100644 index 00000000..974d64e4 --- /dev/null +++ b/docs/.gitignore @@ -0,0 +1,4 @@ +node_modules +*/public/ +.vitepress/cache +.vitepress/dist diff --git a/docs/.vitepress/config.js b/docs/.vitepress/config.js new file mode 100644 index 00000000..c2335e7d --- /dev/null +++ b/docs/.vitepress/config.js @@ -0,0 +1,50 @@ +import baseConfig from '@cakephp/docs-skeleton/config' + +import { createRequire } from "module"; +const require = createRequire(import.meta.url); +const toc_en = require("./toc_en.json"); + +const versions = { + text: "4.x", + items: [ + { text: "4.x (current)", link: "https://book.cakephp.org/authentication/4/en/", target: '_self' }, + { text: "3.x", link: "https://book.cakephp.org/authentication/3/en/", target: '_self' }, + { text: "2.x", link: "https://book.cakephp.org/authentication/2/en/", target: '_self' }, + ], +}; + +// This file contains overrides for .vitepress/config.js +export default { + extends: baseConfig, + srcDir: 'en', + title: 'Authentication plugin', + description: 'Authentication - CakePHP Authentication Plugin Documentation', + base: "/authentication/4/en/", + rewrites: { + "en/:slug*": ":slug*", + }, + sitemap: { + hostname: "https://book.cakephp.org/authentication/4/en/", + }, + themeConfig: { + socialLinks: [ + { icon: "github", link: "https://github.com/cakephp/authentication" }, + ], + editLink: { + pattern: "https://github.com/cakephp/authentication/edit/4.x/docs/:path", + text: "Edit this page on GitHub", + }, + sidebar: toc_en, + nav: [ + { text: "CakePHP Book", link: "https://book.cakephp.org/" }, + { ...versions }, + ], + }, + substitutions: {}, + locales: { + root: { + label: "English", + lang: "en", + }, + }, +}; diff --git a/docs/.vitepress/theme/index.js b/docs/.vitepress/theme/index.js new file mode 100644 index 00000000..e33e19ec --- /dev/null +++ b/docs/.vitepress/theme/index.js @@ -0,0 +1 @@ +export { default } from '@cakephp/docs-skeleton' diff --git a/docs/.vitepress/toc_en.json b/docs/.vitepress/toc_en.json new file mode 100644 index 00000000..fe057a01 --- /dev/null +++ b/docs/.vitepress/toc_en.json @@ -0,0 +1,49 @@ +{ + "/": [ + { + "text": "Getting Started", + "collapsed": false, + "items": [ + { "text": "Quick Start", "link": "/index" } + ] + }, + { + "text": "Core Concepts", + "collapsed": false, + "items": [ + { "text": "Authenticators", "link": "/authenticators" }, + { "text": "Identifiers", "link": "/identifiers" }, + { "text": "Identity Object", "link": "/identity-object" }, + { "text": "Password Hashers", "link": "/password-hashers" } + ] + }, + { + "text": "Integration", + "collapsed": false, + "items": [ + { "text": "Middleware", "link": "/middleware" }, + { "text": "Authentication Component", "link": "/authentication-component" }, + { "text": "View Helper", "link": "/view-helper" }, + { "text": "Testing", "link": "/testing" } + ] + }, + { + "text": "Advanced Topics", + "collapsed": true, + "items": [ + { "text": "User Impersonation", "link": "/impersonation" }, + { "text": "Redirect Validation", "link": "/redirect-validation" }, + { "text": "URL Checkers", "link": "/url-checkers" }, + { "text": "Migration from the AuthComponent", "link": "/migration-from-the-authcomponent" } + ] + }, + { + "text": "Upgrade Guides", + "collapsed": true, + "items": [ + { "text": "Upgrading from 2.x to 3.x", "link": "/upgrade-2-to-3" }, + { "text": "Upgrading from 3.x to 4.x", "link": "/upgrade-3-to-4" } + ] + } + ] +} diff --git a/docs/config/__init__.py b/docs/config/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/docs/config/all.py b/docs/config/all.py deleted file mode 100644 index 909e96d5..00000000 --- a/docs/config/all.py +++ /dev/null @@ -1,48 +0,0 @@ -# Global configuration information used across all the -# translations of documentation. -# -# Import the base theme configuration -from cakephpsphinx.config.all import * - -# The version info for the project you're documenting, acts as replacement for -# |version| and |release|, also used in various other places throughout the -# built documents. -# - -# The full version, including alpha/beta/rc tags. -release = '4.x' - -# The search index version -search_version = 'authentication-4' - -# The marketing display name for the book. -version_name = '' - -# Project name shown in the black header bar -project = 'CakePHP Authentication' - -# Other versions that display in the version picker menu. -version_list = [ - {'name': '1.x', 'number': '/authentication/1', 'title': '1.x'}, - {'name': '2.x', 'number': '/authentication/2', 'title': '2.x'}, - {'name': '3.x', 'number': '/authentication/3', 'title': '3.x'}, - {'name': '4.x', 'number': '/authentication/4', 'title': '4.x', 'current': True}, -] - -# Languages available. -languages = ['en', 'es', 'fr', 'ja'] - -# The GitHub branch name for this version of the docs -# for edit links to point at. -branch = '4.x' - -# Current version being built -version = '4.x' - -show_root_link = True - -repository = 'cakephp/authentication' - -source_path = 'docs/' - -hide_page_contents = ('search', '404', 'contents') diff --git a/docs/en/authentication-component.md b/docs/en/authentication-component.md new file mode 100644 index 00000000..a98f8731 --- /dev/null +++ b/docs/en/authentication-component.md @@ -0,0 +1,123 @@ +# Authentication Component + +You can use the `AuthenticationComponent` to access the result of +authentication, get user identity and logout user. Load the component in your +`AppController::initialize()` like any other component: + +``` php +$this->loadComponent('Authentication.Authentication', [ + 'logoutRedirect' => '/users/login' // Default is false +]); +``` + +Once loaded, the `AuthenticationComponent` will require that all actions have an +authenticated user present, but perform no other access control checks. You can +disable this check for specific actions using `allowUnauthenticated()`: + +``` php +// In your controller's beforeFilter method. +$this->Authentication->allowUnauthenticated(['view']); +``` + +## Accessing the logged in user + +You can get the authenticated user identity data using the authentication +component: + +``` php +$user = $this->Authentication->getIdentity(); +``` + +You can also get the identity directly from the request instance: + +``` php +$user = $request->getAttribute('identity'); +``` + +## Checking the login status + +You can check if the authentication process was successful by accessing the +result object: + +``` php +// Using Authentication component +$result = $this->Authentication->getResult(); + +// Using request object +$result = $request->getAttribute('authentication')->getResult(); + +if ($result->isValid()) { + $user = $request->getAttribute('identity'); +} else { + $this->log($result->getStatus()); + $this->log($result->getErrors()); +} +``` + +The result sets objects status returned from `getStatus()` will match one of +these constants in the Result object: + +- `ResultInterface::SUCCESS`, when successful. +- `ResultInterface::FAILURE_IDENTITY_NOT_FOUND`, when identity could not be found. +- `ResultInterface::FAILURE_CREDENTIALS_INVALID`, when credentials are invalid. +- `ResultInterface::FAILURE_CREDENTIALS_MISSING`, when credentials are missing in the request. +- `ResultInterface::FAILURE_OTHER`, on any other kind of failure. + +The error array returned by `getErrors()` contains **additional** information +coming from the specific system against which the authentication attempt was +made. For example LDAP or OAuth would put errors specific to their +implementation in here for easier logging and debugging the cause. But most of +the included authenticators don't put anything in here. + +## Logging out the identity + +To log an identity out just do: + +``` php +$this->Authentication->logout(); +``` + +If you have set the `logoutRedirect` config, `Authentication::logout()` will +return that value else will return `false`. It won't perform any actual redirection +in either case. + +Alternatively, instead of the component you can also use the service to log out: + +``` php +$return = $request->getAttribute('authentication')->clearIdentity($request, $response); +``` + +The result returned will contain an array like this: + +``` php +[ + 'response' => object(Cake\Http\Response) { ... }, + 'request' => object(Cake\Http\ServerRequest) { ... }, +] +``` + +> [!NOTE] +> This will return an array containing the request and response +> objects. Since both are immutable you'll get new objects back. Depending on your +> context you're working in you'll have to use these instances from now on if you +> want to continue to work with the modified response and request objects. + +## Configure Automatic Identity Checks + +By default `AuthenticationComponent` will automatically enforce an identity to +be present during the `Controller.startup` event. You can have this check +applied during the `Controller.initialize` event instead: + +``` php +// In your controller's initialize() method. +$this->loadComponent('Authentication.Authentication', [ + 'identityCheckEvent' => 'Controller.initialize', +]); +``` + +You can also disable identity checks entirely with the `requireIdentity` +option or by calling `disableIdentityCheck` from the controller's `beforeFilter()` method itself: + +``` php +$this->Authentication->disableIdentityCheck(); +``` diff --git a/docs/en/authentication-component.rst b/docs/en/authentication-component.rst deleted file mode 100644 index 34da6ccf..00000000 --- a/docs/en/authentication-component.rst +++ /dev/null @@ -1,108 +0,0 @@ -Authentication Component -======================== - -You can use the ``AuthenticationComponent`` to access the result of -authentication, get user identity and logout user. Load the component in your -``AppController::initialize()`` like any other component:: - - $this->loadComponent('Authentication.Authentication', [ - 'logoutRedirect' => '/users/login' // Default is false - ]); - -Once loaded, the ``AuthenticationComponent`` will require that all actions have an -authenticated user present, but perform no other access control checks. You can -disable this check for specific actions using ``allowUnauthenticated()``:: - - // In your controller's beforeFilter method. - $this->Authentication->allowUnauthenticated(['view']); - -Accessing the logged in user ----------------------------- - -You can get the authenticated user identity data using the authentication -component:: - - $user = $this->Authentication->getIdentity(); - -You can also get the identity directly from the request instance:: - - $user = $request->getAttribute('identity'); - -Checking the login status -------------------------- - -You can check if the authentication process was successful by accessing the -result object:: - - // Using Authentication component - $result = $this->Authentication->getResult(); - - // Using request object - $result = $request->getAttribute('authentication')->getResult(); - - if ($result->isValid()) { - $user = $request->getAttribute('identity'); - } else { - $this->log($result->getStatus()); - $this->log($result->getErrors()); - } - -The result sets objects status returned from ``getStatus()`` will match one of -these constants in the Result object: - -* ``ResultInterface::SUCCESS``, when successful. -* ``ResultInterface::FAILURE_IDENTITY_NOT_FOUND``, when identity could not be found. -* ``ResultInterface::FAILURE_CREDENTIALS_INVALID``, when credentials are invalid. -* ``ResultInterface::FAILURE_CREDENTIALS_MISSING``, when credentials are missing in the request. -* ``ResultInterface::FAILURE_OTHER``, on any other kind of failure. - -The error array returned by ``getErrors()`` contains **additional** information -coming from the specific system against which the authentication attempt was -made. For example LDAP or OAuth would put errors specific to their -implementation in here for easier logging and debugging the cause. But most of -the included authenticators don't put anything in here. - -Logging out the identity ------------------------- - -To log an identity out just do:: - - $this->Authentication->logout(); - -If you have set the ``logoutRedirect`` config, ``Authentication::logout()`` will -return that value else will return ``false``. It won't perform any actual redirection -in either case. - -Alternatively, instead of the component you can also use the service to log out:: - - $return = $request->getAttribute('authentication')->clearIdentity($request, $response); - -The result returned will contain an array like this:: - - [ - 'response' => object(Cake\Http\Response) { ... }, - 'request' => object(Cake\Http\ServerRequest) { ... }, - ] - -.. note:: - This will return an array containing the request and response - objects. Since both are immutable you'll get new objects back. Depending on your - context you're working in you'll have to use these instances from now on if you - want to continue to work with the modified response and request objects. - -Configure Automatic Identity Checks ------------------------------------ - -By default ``AuthenticationComponent`` will automatically enforce an identity to -be present during the ``Controller.startup`` event. You can have this check -applied during the ``Controller.initialize`` event instead:: - - // In your controller's initialize() method. - $this->loadComponent('Authentication.Authentication', [ - 'identityCheckEvent' => 'Controller.initialize', - ]); - -You can also disable identity checks entirely with the ``requireIdentity`` -option or by calling ``disableIdentityCheck`` from the controller's ``beforeFilter()`` method itself:: - - $this->Authentication->disableIdentityCheck(); diff --git a/docs/en/authenticators.md b/docs/en/authenticators.md new file mode 100644 index 00000000..26c66792 --- /dev/null +++ b/docs/en/authenticators.md @@ -0,0 +1,633 @@ +# Authenticators + +Authenticators handle converting request data into an authentication +operations. They leverage [Identifiers](identifiers) to find a +known [Identity Objects](identity-object). + +## Session + +This authenticator will check the session if it contains user data or +credentials. When using any stateful authenticators like `Form` listed +below, be sure to load `Session` authenticator first so that once +logged in user data is fetched from session itself on subsequent +requests. + +Configuration options: + +- **sessionKey**: The session key for the user data, default is + `Auth` + +## PrimaryKeySession + +This is an improved version of Session that will only store the primary key. +This way the data will always be fetched fresh from the DB and issues like +having to update the identity when updating account data should be gone. + +It also helps to avoid session invalidation. +Session itself stores the entity object including nested objects like DateTime or enums. +With only the ID stored, the invalidation due to objects being modified will also dissolve. + +A default `TokenIdentifier` is provided that looks up users by their `id` field, +so minimal configuration is required: + +``` php +$service->loadAuthenticator('Authentication.PrimaryKeySession'); +``` + +Configuration options: + +- **idField**: The field in the database table to look up. Default is `id`. +- **identifierKey**: The key used to store/retrieve the primary key from session data. + Default is `key`. + +For custom lookup fields, the `idField` and `identifierKey` options propagate +to the default identifier automatically: + +``` php +$service->loadAuthenticator('Authentication.PrimaryKeySession', [ + 'idField' => 'uuid', +]); +``` + +You can also provide a fully custom identifier configuration if needed: + +``` php +$service->loadAuthenticator('Authentication.PrimaryKeySession', [ + 'identifier' => [ + 'Authentication.Token' => [ + 'tokenField' => 'id', + 'dataField' => 'key', + 'resolver' => 'Authentication.Orm', + ], + ], +]); +``` + +## Form + +Looks up the data in the request body, usually when a form submit +happens via POST / PUT. + +Configuration options: + +- **loginUrl**: The login URL, string or array of URLs. Default is + `null` and all pages will be checked. +- **fields**: Array that maps `username` and `password` to the + specified POST data fields. +- **urlChecker**: The URL checker class or object. Default is + `DefaultUrlChecker`. +- **useRegex**: Whether or not to use regular expressions for URL + matching. Default is `false`. +- **checkFullUrl**: Whether or not to check full URL including the query + string. Useful when a login form is on a different subdomain. Default is + `false`. This option does not work well when preserving unauthenticated + redirects in the query string. + +If you are building an API and want to accept credentials via JSON requests make +sure you have the `BodyParserMiddleware` applied **before** the +`AuthenticationMiddleware`. + +> [!WARNING] +> If you use the array syntax for the URL, the URL will be +> generated by the CakePHP router. The result **might** differ from what you +> actually have in the request URI depending on your route handling. So +> consider this to be case sensitive! + +## Token + +The token authenticator can authenticate a request based on a token that +comes along with the request in the headers or in the request +parameters. This requires a token column in the Users table, so comparison can be made between the received token and stored token. + +Configuration options: + +- **queryParam**: Name of the query parameter. Configure it if you want + to get the token from the query parameters. +- **header**: Name of the header. Configure it if you want to get the + token from the header. +- **tokenPrefix**: The optional token prefix. + +An example of getting a token from a header, or query string would be: + +``` php +$service->loadAuthenticator('Authentication.Token', [ + 'queryParam' => 'token', + 'header' => 'Authorization', + 'tokenPrefix' => 'Token' +]); +``` + +The above would read the `token` GET parameter or the `Authorization` header +as long as the token was preceded by `Token` and a space. + +The token will always be passed to the configured identifier as follows: + +``` php +[ + 'token' => '{token-value}', +] +``` + +## JWT + +The JWT authenticator gets the [JWT token](https://jwt.io/) from the +header or query param and either returns the payload directly or passes +it to the identifiers to verify them against another datasource for +example. + +- **header**: The header line to check for the token. The default is + `Authorization`. +- **queryParam**: The query param to check for the token. The default + is `token`. +- **tokenPrefix**: The token prefix. Default is `bearer`. +- **algorithm**: The hashing algorithm for Firebase JWT. + Default is `'HS256'`. +- **returnPayload**: To return or not return the token payload directly + without going through the identifiers. Default is `true`. +- **secretKey**: Default is `null` but you’re **required** to pass a + secret key if you’re not in the context of a CakePHP application that + provides it through `Security::salt()`. +- **jwks**: Default is `null`. Associative array with a `'keys'` key. + If provided will be used instead of the secret key. + +You need to add the lib [firebase/php-jwt](https://github.com/firebase/php-jwt) +v7.0 or above to your app to use the `JwtAuthenticator`. + +By default the `JwtAuthenticator` uses `HS256` symmetric key algorithm and uses +the value of `Cake\Utility\Security::salt()` as encryption key. +For enhanced security one can instead use the `RS256` asymmetric key algorithm. +You can generate the required keys for that as follows: + +``` text +# generate private key +openssl genrsa -out config/jwt.key 1024 +# generate public key +openssl rsa -in config/jwt.key -outform PEM -pubout -out config/jwt.pem +``` + +The `jwt.key` file is the private key and should be kept safe. +The `jwt.pem` file is the public key. This file should be used when you need to verify tokens +created by external applications, eg: mobile apps. + +The following example allows you to identify the user based on the `sub` (subject) of the +token by using `JwtSubject` identifier, and configures the `Authenticator` to use public key +for token verification. + +Add the following to your `Application` class: + +``` php +public function getAuthenticationService(ServerRequestInterface $request): AuthenticationServiceInterface +{ + $service = new AuthenticationService(); + // ... + $service->loadAuthenticator('Authentication.Jwt', [ + 'identifier' => 'Authentication.JwtSubject', + 'secretKey' => file_get_contents(CONFIG . '/jwt.key'), + 'algorithm' => 'RS256', + 'returnPayload' => false + ]); + + return $service; +} +``` + +In your `UsersController`: + +``` php +use Firebase\JWT\JWT; + +public function login() +{ + $result = $this->Authentication->getResult(); + if ($result->isValid()) { + $privateKey = file_get_contents(CONFIG . '/jwt.key'); + $user = $result->getData(); + $payload = [ + 'iss' => 'myapp', + 'sub' => $user->id, + 'exp' => time() + 60, + ]; + $json = [ + 'token' => JWT::encode($payload, $privateKey, 'RS256'), + ]; + } else { + $this->response = $this->response->withStatus(401); + $json = []; + } + $this->set(compact('json')); + $this->viewBuilder()->setOption('serialize', 'json'); +} +``` + +Using a JWKS fetched from an external JWKS endpoint is supported as well: + +``` php +// Application.php +public function getAuthenticationService(ServerRequestInterface $request): AuthenticationServiceInterface +{ + $service = new AuthenticationService(); + // ... + + $jwksUrl = 'https://appleid.apple.com/auth/keys'; + + // Set of keys. The "keys" key is required. Additionally keys require a "alg" key. + // Add it manually to your JWK array if it doesn't already exist. + $jsonWebKeySet = Cache::remember('jwks-' . md5($jwksUrl), function () use ($jwksUrl) { + $http = new Client(); + $response = $http->get($jwksUrl); + return $response->getJson(); + }); + + $service->loadAuthenticator('Authentication.Jwt', [ + 'identifier' => 'Authentication.JwtSubject', + 'jwks' => $jsonWebKeySet, + 'returnPayload' => false + ]); +} +``` + +The JWKS resource will return the same set of keys most of the time. +Applications should cache these resources, but they also need to be +prepared to handle signing key rotations. + +> [!WARNING] +> Applications need to pick a cache lifetime that balances performance and security. +> This is particularly important in situations where a private key is compromised. + +Beside from sharing the public key file to external application, you can +distribute it via a JWKS endpoint by configuring your app as follows: + +``` php +// config/routes.php +$builder->setExtensions('json'); +$builder->connect('/.well-known/:controller/*', [ + 'action' => 'index', +], [ + 'controller' => '(jwks)', +]); // connect /.well-known/jwks.json to JwksController + +// controller/JwksController.php +public function index() +{ + $pubKey = file_get_contents(CONFIG . './jwt.pem'); + $res = openssl_pkey_get_public($pubKey); + $detail = openssl_pkey_get_details($res); + $key = [ + 'kty' => 'RSA', + 'alg' => 'RS256', + 'use' => 'sig', + 'e' => JWT::urlsafeB64Encode($detail['rsa']['e']), + 'n' => JWT::urlsafeB64Encode($detail['rsa']['n']), + ]; + $keys['keys'][] = $key; + + $this->viewBuilder()->setClassName('Json'); + $this->set(compact('keys')); + $this->viewBuilder()->setOption('serialize', 'keys'); +} +``` + +Refer to or for +more information about JWKS. + +## HttpBasic + +See + +> [!NOTE] +> This authenticator will halt the request when authentication credentials are missing or invalid. + +Configuration options: + +- **realm**: Default is `$_SERVER['SERVER_NAME']` override it as + needed. + +## HttpDigest + +See + +> [!NOTE] +> This authenticator will halt the request when authentication credentials are missing or invalid. + +Configuration options: + +- **realm**: Default is `null` +- **qop**: Default is `auth` +- **nonce**: Default is `uniqid(''),` +- **opaque**: Default is `null` + +## Cookie Authenticator aka "Remember Me" + +The Cookie Authenticator allows you to implement the “remember me” +feature for your login forms. + +Just make sure your login form has a field that matches the field name +that is configured in this authenticator. + +To encrypt and decrypt your cookie make sure you added the +EncryptedCookieMiddleware to your app *before* the +AuthenticationMiddleware. + +Configuration options: + +- **rememberMeField**: Default is `remember_me` + +- **cookie**: Array of cookie options: + + - **name**: Cookie name, default is `CookieAuth` + - **expires**: Expiration, default is `null` + - **path**: Path, default is `/` + - **domain**: Domain, default is an empty string. + - **secure**: Bool, default is `false` + - **httponly**: Bool, default is `false` + - **value**: Value, default is an empty string. + - **samesite**: String/null The value for the same site attribute. + + The defaults for the various options besides `cookie.name` will be those + set for the `Cake\Http\Cookie\Cookie` class. See [Cookie::setDefaults()](https://api.cakephp.org/4.0/class-Cake.Http.Cookie.Cookie.html#setDefaults) + for the default values. + +- **fields**: Array that maps `username` and `password` to the + specified identity fields. + +- **urlChecker**: The URL checker class or object. Default is + `DefaultUrlChecker`. + +- **loginUrl**: The login URL, string or array of URLs. Default is + `null` and all pages will be checked. + +- **passwordHasher**: Password hasher to use for token hashing. Default + is `DefaultPasswordHasher::class`. + +- **salt**: When `false` no salt is used. When a string is passed that value is used as a salt value. + When `true` the default Security.salt is used. Default is `true`. When a salt is used, the cookie value + will contain hash(username + password + hmac(username + password, salt)). This helps harden tokens against possible + database leaks and enables cookie values to be invalidated by rotating the salt value. + +### Usage + +The cookie authenticator can be added to a Form & Session based +authentication system. Cookie authentication will automatically re-login users +after their session expires for as long as the cookie is valid. If a user is +explicity logged out via `AuthenticationComponent::logout()` the +authentication cookie is **also destroyed**. An example configuration would be: + +``` php +// In Application::getAuthenticationService() + +// Reuse fields in multiple authenticators. +$fields = [ + AbstractIdentifier::CREDENTIAL_USERNAME => 'email', + AbstractIdentifier::CREDENTIAL_PASSWORD => 'password', +]; + +// Put form authentication first so that users can re-login via +// the login form if necessary. +$service->loadAuthenticator('Authentication.Form', [ + 'identifier' => 'Authentication.Password', + 'fields' => $fields, + 'loginUrl' => '/users/login', +]); +// Then use sessions if they are active. +$service->loadAuthenticator('Authentication.Session', [ + 'identifier' => 'Authentication.Password', +]); + +// If the user is on the login page, check for a cookie as well. +$service->loadAuthenticator('Authentication.Cookie', [ + 'identifier' => 'Authentication.Password', + 'fields' => $fields, + 'loginUrl' => '/users/login', +]); +``` + +You'll also need to add a checkbox to your login form to have cookies created: + +``` php +// In your login view +Form->control('remember_me', ['type' => 'checkbox']); +``` + +After logging in, if the checkbox was checked you should see a `CookieAuth` +cookie in your browser dev tools. The cookie stores the username field and +a hashed token that is used to reauthenticate later. + +## Environment Variables + +The `EnvironmentAuthenticator` can authenticate users based on mapped +environment variables exposed by the webserver. This enables authentication via +[Shibboleth](https://shibboleth.atlassian.net/wiki/spaces/CONCEPT/overview) +and similar SAML 1.1 implementations. An example configuration is: + +``` php +// Configure a token identifier that maps `USER_ID` to the +// username column +$identifier = [ + 'Authentication.Token' => [ + 'tokenField' => 'username', + 'dataField' => 'USER_NAME', + ], +]; + +$service->loadAuthenticator('Authentication.Environment', [ + 'identifier' => $identifier, + 'loginUrl' => '/sso', + 'fields' => [ + // Choose which environment variables exposed by your + // authentication provider are used to authenticate + // in your application. + 'USER_NAME', + ], +]); +``` + +::: info Added in version 2.10.0 +`EnvironmentAuthenticator` was added. +::: + +## Events + +There is only one event that is fired by authentication: +`Authentication.afterIdentify`. + +If you don’t know what events are and how to use them [check the +documentation](https://book.cakephp.org/5/en/core-libraries/events.html). + +The `Authentication.afterIdentify` event is fired by the +`AuthenticationComponent` after an identity was successfully +identified. + +The event contains the following data: + +- **provider**: An object that implements + `\Authentication\Authenticator\AuthenticatorInterface` +- **identity**: An object that implements `\ArrayAccess` +- **service**: An object that implements + `\Authentication\AuthenticationServiceInterface` + +The subject of the event will be the current controller instance the +AuthenticationComponent is attached to. + +But the event is only fired if the authenticator that was used to +identify the identity is *not* persistent and *not* stateless. The +reason for this is that the event would be fired every time because the +session authenticator or token for example would trigger it every time +for every request. + +From the included authenticators only the FormAuthenticator will cause +the event to be fired. After that the session authenticator will provide +the identity. + +## URL Checkers + +Some authenticators like `Form` or `Cookie` should be executed only +on certain pages like `/login` page. This can be achieved using URL +Checkers. + +By default a `DefaultUrlChecker` is used, which uses string URLs for +comparison with support for regex check. + +Configuration options: + +- **useRegex**: Whether or not to use regular expressions for URL + matching. Default is `false`. +- **checkFullUrl**: Whether or not to check full URL. Useful when a + login form is on a different subdomain. Default is `false`. + +A custom URL checker can be implemented for example if a support for +framework specific URLs is needed. In this case the +`Authentication\UrlChecker\UrlCheckerInterface` should +be implemented. + +For more details about URL Checkers [see this documentation +page](url-checkers). + +## Getting the Successful Authenticator or Identifier + +After a user has been authenticated you may want to inspect or interact with the +Authenticator that successfully authenticated the user: + +``` php +// In a controller action +$service = $this->request->getAttribute('authentication'); + +// Will be null on authentication failure, or an authenticator. +$authenticator = $service->getAuthenticationProvider(); +``` + +You can also get the identifier that identified the user as well: + +``` php +// In a controller action +$service = $this->request->getAttribute('authentication'); + +// Will be null on authentication failure, or an identifier. +$identifier = $service->getIdentificationProvider(); +``` + +## Using Stateless Authenticators with Stateful Authenticators + +When using `HttpBasic`, `HttpDigest` with other authenticators, +you should remember that these authenticators will halt the request when +authentication credentials are missing or invalid. This is necessary as these +authenticators must send specific challenge headers in the response: + +``` php +use Authentication\AuthenticationService; + +// Instantiate the service +$service = new AuthenticationService(); + +// Define identifiers +$passwordIdentifier = [ + 'Authentication.Password' => [ + 'fields' => [ + 'username' => 'email', + 'password' => 'password' + ] + ], +]; + +// Load the authenticators leaving Basic as the last one. +$service->loadAuthenticator('Authentication.Session', [ + 'identifier' => $passwordIdentifier, +]); +$service->loadAuthenticator('Authentication.Form', [ + 'identifier' => $passwordIdentifier, +]); +$service->loadAuthenticator('Authentication.HttpBasic', [ + 'identifier' => 'Authentication.Token', +]); +``` + +If you want to combine `HttpBasic` or `HttpDigest` with other +authenticators, be aware that these authenticators will abort the request and +force a browser dialog. + +## Handling Unauthenticated Errors + +The `AuthenticationComponent` will raise an exception when users are not +authenticated. You can convert this exception into a redirect using the +`unauthenticatedRedirect` when configuring the `AuthenticationService`. + +You can also pass the current request target URI as a query parameter +using the `queryParam` option: + +``` php +// In the getAuthenticationService() method of your src/Application.php + +$service = new AuthenticationService(); + +// Configure unauthenticated redirect +$service->setConfig([ + 'unauthenticatedRedirect' => '/users/login', + 'queryParam' => 'redirect', +]); +``` + +Then in your controller's login method you can use `getLoginRedirect()` to get +the redirect target safely from the query string parameter: + +``` php +public function login() +{ + $result = $this->Authentication->getResult(); + + // Regardless of POST or GET, redirect if user is logged in + if ($result->isValid()) { + // Use the redirect parameter if present. + $target = $this->Authentication->getLoginRedirect(); + if (!$target) { + $target = ['controller' => 'Pages', 'action' => 'display', 'home']; + } + return $this->redirect($target); + } +} +``` + +## Having Multiple Authentication Flows + +In an application that provides both an API and a web interface +you may want different authentication configurations based on +whether the request is an API request or not. For example, you may use JWT +authentication for your API, but sessions for your web interface. To support +this flow you can return different authentication services based on the URL +path, or any other request attribute: + +``` php +public function getAuthenticationService( + ServerRequestInterface $request +): AuthenticationServiceInterface { + $service = new AuthenticationService(); + + // Configuration common to both the API and web goes here. + + if ($request->getParam('prefix') == 'Api') { + // Include API specific authenticators + } else { + // Web UI specific authenticators. + } + + return $service; +} +``` diff --git a/docs/en/authenticators.rst b/docs/en/authenticators.rst deleted file mode 100644 index bb1f509e..00000000 --- a/docs/en/authenticators.rst +++ /dev/null @@ -1,610 +0,0 @@ -Authenticators -############## - -Authenticators handle converting request data into an authentication -operations. They leverage :doc:`/identifiers` to find a -known :doc:`/identity-object`. - -Session -======= - -This authenticator will check the session if it contains user data or -credentials. When using any stateful authenticators like ``Form`` listed -below, be sure to load ``Session`` authenticator first so that once -logged in user data is fetched from session itself on subsequent -requests. - -Configuration options: - -- **sessionKey**: The session key for the user data, default is - ``Auth`` - -PrimaryKeySession -================= - -This is an improved version of Session that will only store the primary key. -This way the data will always be fetched fresh from the DB and issues like -having to update the identity when updating account data should be gone. - -It also helps to avoid session invalidation. -Session itself stores the entity object including nested objects like DateTime or enums. -With only the ID stored, the invalidation due to objects being modified will also dissolve. - -A default ``TokenIdentifier`` is provided that looks up users by their ``id`` field, -so minimal configuration is required:: - - $service->loadAuthenticator('Authentication.PrimaryKeySession'); - -Configuration options: - -- **idField**: The field in the database table to look up. Default is ``id``. -- **identifierKey**: The key used to store/retrieve the primary key from session data. - Default is ``key``. - -For custom lookup fields, the ``idField`` and ``identifierKey`` options propagate -to the default identifier automatically:: - - $service->loadAuthenticator('Authentication.PrimaryKeySession', [ - 'idField' => 'uuid', - ]); - -You can also provide a fully custom identifier configuration if needed:: - - $service->loadAuthenticator('Authentication.PrimaryKeySession', [ - 'identifier' => [ - 'Authentication.Token' => [ - 'tokenField' => 'id', - 'dataField' => 'key', - 'resolver' => 'Authentication.Orm', - ], - ], - ]); - -Form -==== - -Looks up the data in the request body, usually when a form submit -happens via POST / PUT. - -Configuration options: - -- **loginUrl**: The login URL, string or array of URLs. Default is - ``null`` and all pages will be checked. -- **fields**: Array that maps ``username`` and ``password`` to the - specified POST data fields. -- **urlChecker**: The URL checker class or object. Default is - ``DefaultUrlChecker``. -- **useRegex**: Whether or not to use regular expressions for URL - matching. Default is ``false``. -- **checkFullUrl**: Whether or not to check full URL including the query - string. Useful when a login form is on a different subdomain. Default is - ``false``. This option does not work well when preserving unauthenticated - redirects in the query string. - -If you are building an API and want to accept credentials via JSON requests make -sure you have the ``BodyParserMiddleware`` applied **before** the -``AuthenticationMiddleware``. - -.. warning:: - If you use the array syntax for the URL, the URL will be - generated by the CakePHP router. The result **might** differ from what you - actually have in the request URI depending on your route handling. So - consider this to be case sensitive! - -Token -===== - -The token authenticator can authenticate a request based on a token that -comes along with the request in the headers or in the request -parameters. This requires a token column in the Users table, so comparison can be made between the received token and stored token. - -Configuration options: - -- **queryParam**: Name of the query parameter. Configure it if you want - to get the token from the query parameters. -- **header**: Name of the header. Configure it if you want to get the - token from the header. -- **tokenPrefix**: The optional token prefix. - -An example of getting a token from a header, or query string would be:: - - $service->loadAuthenticator('Authentication.Token', [ - 'queryParam' => 'token', - 'header' => 'Authorization', - 'tokenPrefix' => 'Token' - ]); - -The above would read the ``token`` GET parameter or the ``Authorization`` header -as long as the token was preceded by ``Token`` and a space. - -The token will always be passed to the configured identifier as follows:: - - [ - 'token' => '{token-value}', - ] - -JWT -=== - -The JWT authenticator gets the `JWT token `__ from the -header or query param and either returns the payload directly or passes -it to the identifiers to verify them against another datasource for -example. - -- **header**: The header line to check for the token. The default is - ``Authorization``. -- **queryParam**: The query param to check for the token. The default - is ``token``. -- **tokenPrefix**: The token prefix. Default is ``bearer``. -- **algorithm**: The hashing algorithm for Firebase JWT. - Default is ``'HS256'``. -- **returnPayload**: To return or not return the token payload directly - without going through the identifiers. Default is ``true``. -- **secretKey**: Default is ``null`` but you’re **required** to pass a - secret key if you’re not in the context of a CakePHP application that - provides it through ``Security::salt()``. -- **jwks**: Default is ``null``. Associative array with a ``'keys'`` key. - If provided will be used instead of the secret key. - -You need to add the lib `firebase/php-jwt `_ -v7.0 or above to your app to use the ``JwtAuthenticator``. - -By default the ``JwtAuthenticator`` uses ``HS256`` symmetric key algorithm and uses -the value of ``Cake\Utility\Security::salt()`` as encryption key. -For enhanced security one can instead use the ``RS256`` asymmetric key algorithm. -You can generate the required keys for that as follows:: - - # generate private key - openssl genrsa -out config/jwt.key 1024 - # generate public key - openssl rsa -in config/jwt.key -outform PEM -pubout -out config/jwt.pem - -The ``jwt.key`` file is the private key and should be kept safe. -The ``jwt.pem`` file is the public key. This file should be used when you need to verify tokens -created by external applications, eg: mobile apps. - -The following example allows you to identify the user based on the ``sub`` (subject) of the -token by using ``JwtSubject`` identifier, and configures the ``Authenticator`` to use public key -for token verification. - -Add the following to your ``Application`` class:: - - public function getAuthenticationService(ServerRequestInterface $request): AuthenticationServiceInterface - { - $service = new AuthenticationService(); - // ... - $service->loadAuthenticator('Authentication.Jwt', [ - 'identifier' => 'Authentication.JwtSubject', - 'secretKey' => file_get_contents(CONFIG . '/jwt.key'), - 'algorithm' => 'RS256', - 'returnPayload' => false - ]); - - return $service; - } - -In your ``UsersController``:: - - use Firebase\JWT\JWT; - - public function login() - { - $result = $this->Authentication->getResult(); - if ($result->isValid()) { - $privateKey = file_get_contents(CONFIG . '/jwt.key'); - $user = $result->getData(); - $payload = [ - 'iss' => 'myapp', - 'sub' => $user->id, - 'exp' => time() + 60, - ]; - $json = [ - 'token' => JWT::encode($payload, $privateKey, 'RS256'), - ]; - } else { - $this->response = $this->response->withStatus(401); - $json = []; - } - $this->set(compact('json')); - $this->viewBuilder()->setOption('serialize', 'json'); - } - -Using a JWKS fetched from an external JWKS endpoint is supported as well:: - - // Application.php - public function getAuthenticationService(ServerRequestInterface $request): AuthenticationServiceInterface - { - $service = new AuthenticationService(); - // ... - - $jwksUrl = 'https://appleid.apple.com/auth/keys'; - - // Set of keys. The "keys" key is required. Additionally keys require a "alg" key. - // Add it manually to your JWK array if it doesn't already exist. - $jsonWebKeySet = Cache::remember('jwks-' . md5($jwksUrl), function () use ($jwksUrl) { - $http = new Client(); - $response = $http->get($jwksUrl); - return $response->getJson(); - }); - - $service->loadAuthenticator('Authentication.Jwt', [ - 'identifier' => 'Authentication.JwtSubject', - 'jwks' => $jsonWebKeySet, - 'returnPayload' => false - ]); - } - -The JWKS resource will return the same set of keys most of the time. -Applications should cache these resources, but they also need to be -prepared to handle signing key rotations. - -.. warning:: - - Applications need to pick a cache lifetime that balances performance and security. - This is particularly important in situations where a private key is compromised. - -Beside from sharing the public key file to external application, you can -distribute it via a JWKS endpoint by configuring your app as follows:: - - // config/routes.php - $builder->setExtensions('json'); - $builder->connect('/.well-known/:controller/*', [ - 'action' => 'index', - ], [ - 'controller' => '(jwks)', - ]); // connect /.well-known/jwks.json to JwksController - - // controller/JwksController.php - public function index() - { - $pubKey = file_get_contents(CONFIG . './jwt.pem'); - $res = openssl_pkey_get_public($pubKey); - $detail = openssl_pkey_get_details($res); - $key = [ - 'kty' => 'RSA', - 'alg' => 'RS256', - 'use' => 'sig', - 'e' => JWT::urlsafeB64Encode($detail['rsa']['e']), - 'n' => JWT::urlsafeB64Encode($detail['rsa']['n']), - ]; - $keys['keys'][] = $key; - - $this->viewBuilder()->setClassName('Json'); - $this->set(compact('keys')); - $this->viewBuilder()->setOption('serialize', 'keys'); - } - -Refer to https://datatracker.ietf.org/doc/html/rfc7517 or https://auth0.com/docs/tokens/json-web-tokens/json-web-key-sets for -more information about JWKS. - -HttpBasic -========= - -See https://en.wikipedia.org/wiki/Basic_access_authentication - -.. note:: - - This authenticator will halt the request when authentication credentials are missing or invalid. - -Configuration options: - -- **realm**: Default is ``$_SERVER['SERVER_NAME']`` override it as - needed. - -HttpDigest -========== - -See https://en.wikipedia.org/wiki/Digest_access_authentication - -.. note:: - - This authenticator will halt the request when authentication credentials are missing or invalid. - -Configuration options: - -- **realm**: Default is ``null`` -- **qop**: Default is ``auth`` -- **nonce**: Default is ``uniqid(''),`` -- **opaque**: Default is ``null`` - -Cookie Authenticator aka "Remember Me" -====================================== - -The Cookie Authenticator allows you to implement the “remember me” -feature for your login forms. - -Just make sure your login form has a field that matches the field name -that is configured in this authenticator. - -To encrypt and decrypt your cookie make sure you added the -EncryptedCookieMiddleware to your app *before* the -AuthenticationMiddleware. - -Configuration options: - -- **rememberMeField**: Default is ``remember_me`` -- **cookie**: Array of cookie options: - - - **name**: Cookie name, default is ``CookieAuth`` - - **expires**: Expiration, default is ``null`` - - **path**: Path, default is ``/`` - - **domain**: Domain, default is an empty string. - - **secure**: Bool, default is ``false`` - - **httponly**: Bool, default is ``false`` - - **value**: Value, default is an empty string. - - **samesite**: String/null The value for the same site attribute. - - The defaults for the various options besides ``cookie.name`` will be those - set for the ``Cake\Http\Cookie\Cookie`` class. See `Cookie::setDefaults() `_ - for the default values. - -- **fields**: Array that maps ``username`` and ``password`` to the - specified identity fields. -- **urlChecker**: The URL checker class or object. Default is - ``DefaultUrlChecker``. -- **loginUrl**: The login URL, string or array of URLs. Default is - ``null`` and all pages will be checked. -- **passwordHasher**: Password hasher to use for token hashing. Default - is ``DefaultPasswordHasher::class``. -- **salt**: When ``false`` no salt is used. When a string is passed that value is used as a salt value. - When ``true`` the default Security.salt is used. Default is ``true``. When a salt is used, the cookie value - will contain `hash(username + password + hmac(username + password, salt))`. This helps harden tokens against possible - database leaks and enables cookie values to be invalidated by rotating the salt value. - -Usage ------ - -The cookie authenticator can be added to a Form & Session based -authentication system. Cookie authentication will automatically re-login users -after their session expires for as long as the cookie is valid. If a user is -explicity logged out via ``AuthenticationComponent::logout()`` the -authentication cookie is **also destroyed**. An example configuration would be:: - - // In Application::getAuthenticationService() - - // Reuse fields in multiple authenticators. - $fields = [ - AbstractIdentifier::CREDENTIAL_USERNAME => 'email', - AbstractIdentifier::CREDENTIAL_PASSWORD => 'password', - ]; - - // Put form authentication first so that users can re-login via - // the login form if necessary. - $service->loadAuthenticator('Authentication.Form', [ - 'identifier' => 'Authentication.Password', - 'fields' => $fields, - 'loginUrl' => '/users/login', - ]); - // Then use sessions if they are active. - $service->loadAuthenticator('Authentication.Session', [ - 'identifier' => 'Authentication.Password', - ]); - - // If the user is on the login page, check for a cookie as well. - $service->loadAuthenticator('Authentication.Cookie', [ - 'identifier' => 'Authentication.Password', - 'fields' => $fields, - 'loginUrl' => '/users/login', - ]); - -You'll also need to add a checkbox to your login form to have cookies created:: - - // In your login view - Form->control('remember_me', ['type' => 'checkbox']); - -After logging in, if the checkbox was checked you should see a ``CookieAuth`` -cookie in your browser dev tools. The cookie stores the username field and -a hashed token that is used to reauthenticate later. - -Environment Variables -===================== - -The ``EnvironmentAuthenticator`` can authenticate users based on mapped -environment variables exposed by the webserver. This enables authentication via -`Shibboleth `_ -and similar SAML 1.1 implementations. An example configuration is:: - - // Configure a token identifier that maps `USER_ID` to the - // username column - $identifier = [ - 'Authentication.Token' => [ - 'tokenField' => 'username', - 'dataField' => 'USER_NAME', - ], - ]; - - $service->loadAuthenticator('Authentication.Environment', [ - 'identifier' => $identifier, - 'loginUrl' => '/sso', - 'fields' => [ - // Choose which environment variables exposed by your - // authentication provider are used to authenticate - // in your application. - 'USER_NAME', - ], - ]); - -.. versionadded:: 2.10.0 - ``EnvironmentAuthenticator`` was added. - -Events -====== - -There is only one event that is fired by authentication: -``Authentication.afterIdentify``. - -If you don’t know what events are and how to use them `check the -documentation `__. - -The ``Authentication.afterIdentify`` event is fired by the -``AuthenticationComponent`` after an identity was successfully -identified. - -The event contains the following data: - -- **provider**: An object that implements - ``\Authentication\Authenticator\AuthenticatorInterface`` -- **identity**: An object that implements ``\ArrayAccess`` -- **service**: An object that implements - ``\Authentication\AuthenticationServiceInterface`` - -The subject of the event will be the current controller instance the -AuthenticationComponent is attached to. - -But the event is only fired if the authenticator that was used to -identify the identity is *not* persistent and *not* stateless. The -reason for this is that the event would be fired every time because the -session authenticator or token for example would trigger it every time -for every request. - -From the included authenticators only the FormAuthenticator will cause -the event to be fired. After that the session authenticator will provide -the identity. - -URL Checkers -============ - -Some authenticators like ``Form`` or ``Cookie`` should be executed only -on certain pages like ``/login`` page. This can be achieved using URL -Checkers. - -By default a ``DefaultUrlChecker`` is used, which uses string URLs for -comparison with support for regex check. - -Configuration options: - -- **useRegex**: Whether or not to use regular expressions for URL - matching. Default is ``false``. -- **checkFullUrl**: Whether or not to check full URL. Useful when a - login form is on a different subdomain. Default is ``false``. - -A custom URL checker can be implemented for example if a support for -framework specific URLs is needed. In this case the -``Authentication\UrlChecker\UrlCheckerInterface`` should -be implemented. - -For more details about URL Checkers :doc:`see this documentation -page `. - -Getting the Successful Authenticator or Identifier -================================================== - -After a user has been authenticated you may want to inspect or interact with the -Authenticator that successfully authenticated the user:: - - // In a controller action - $service = $this->request->getAttribute('authentication'); - - // Will be null on authentication failure, or an authenticator. - $authenticator = $service->getAuthenticationProvider(); - -You can also get the identifier that identified the user as well:: - - // In a controller action - $service = $this->request->getAttribute('authentication'); - - // Will be null on authentication failure, or an identifier. - $identifier = $service->getIdentificationProvider(); - - -Using Stateless Authenticators with Stateful Authenticators -=========================================================== - -When using ``HttpBasic``, ``HttpDigest`` with other authenticators, -you should remember that these authenticators will halt the request when -authentication credentials are missing or invalid. This is necessary as these -authenticators must send specific challenge headers in the response:: - - use Authentication\AuthenticationService; - - // Instantiate the service - $service = new AuthenticationService(); - - // Define identifiers - $passwordIdentifier = [ - 'Authentication.Password' => [ - 'fields' => [ - 'username' => 'email', - 'password' => 'password' - ] - ], - ]; - - // Load the authenticators leaving Basic as the last one. - $service->loadAuthenticator('Authentication.Session', [ - 'identifier' => $passwordIdentifier, - ]); - $service->loadAuthenticator('Authentication.Form', [ - 'identifier' => $passwordIdentifier, - ]); - $service->loadAuthenticator('Authentication.HttpBasic', [ - 'identifier' => 'Authentication.Token', - ]); - -If you want to combine ``HttpBasic`` or ``HttpDigest`` with other -authenticators, be aware that these authenticators will abort the request and -force a browser dialog. - -Handling Unauthenticated Errors -================================ - -The ``AuthenticationComponent`` will raise an exception when users are not -authenticated. You can convert this exception into a redirect using the -``unauthenticatedRedirect`` when configuring the ``AuthenticationService``. - -You can also pass the current request target URI as a query parameter -using the ``queryParam`` option:: - - // In the getAuthenticationService() method of your src/Application.php - - $service = new AuthenticationService(); - - // Configure unauthenticated redirect - $service->setConfig([ - 'unauthenticatedRedirect' => '/users/login', - 'queryParam' => 'redirect', - ]); - -Then in your controller's login method you can use ``getLoginRedirect()`` to get -the redirect target safely from the query string parameter:: - - public function login() - { - $result = $this->Authentication->getResult(); - - // Regardless of POST or GET, redirect if user is logged in - if ($result->isValid()) { - // Use the redirect parameter if present. - $target = $this->Authentication->getLoginRedirect(); - if (!$target) { - $target = ['controller' => 'Pages', 'action' => 'display', 'home']; - } - return $this->redirect($target); - } - } - -Having Multiple Authentication Flows -==================================== - -In an application that provides both an API and a web interface -you may want different authentication configurations based on -whether the request is an API request or not. For example, you may use JWT -authentication for your API, but sessions for your web interface. To support -this flow you can return different authentication services based on the URL -path, or any other request attribute:: - - public function getAuthenticationService( - ServerRequestInterface $request - ): AuthenticationServiceInterface { - $service = new AuthenticationService(); - - // Configuration common to both the API and web goes here. - - if ($request->getParam('prefix') == 'Api') { - // Include API specific authenticators - } else { - // Web UI specific authenticators. - } - - return $service; - } diff --git a/docs/en/conf.py b/docs/en/conf.py deleted file mode 100644 index f638bda2..00000000 --- a/docs/en/conf.py +++ /dev/null @@ -1,9 +0,0 @@ -import sys, os - -# Append the top level directory of the docs, so we can import from the config dir. -sys.path.insert(0, os.path.abspath('..')) - -# Pull in all the configuration options defined in the global config file.. -from config.all import * - -language = 'en' diff --git a/docs/en/contents.md b/docs/en/contents.md new file mode 100644 index 00000000..80b75fcf --- /dev/null +++ b/docs/en/contents.md @@ -0,0 +1,17 @@ +# Contents + +### CakePHP Authentication + +- [Quick Start](index) +- [Authenticators](authenticators) +- [Identifiers](identifiers) +- [Password Hashers](password-hashers) +- [Identity Objects](identity-object) +- [Middleware](middleware) +- [Authentication Component](authentication-component) +- [Testing with Authentication](testing) +- [User Impersonation](impersonation) +- [URL Checkers](url-checkers) +- [View Helper](view-helper) +- [Migration from the AuthComponent](migration-from-the-authcomponent) +- [Upgrading from 2.x to 3.x](upgrade-2-to-3) diff --git a/docs/en/contents.rst b/docs/en/contents.rst deleted file mode 100644 index fb7ef565..00000000 --- a/docs/en/contents.rst +++ /dev/null @@ -1,20 +0,0 @@ -Contents -######## - -.. toctree:: - :maxdepth: 2 - :caption: CakePHP Authentication - - /index - /authenticators - /identifiers - /password-hashers - /identity-object - /middleware - /authentication-component - /testing - /impersonation - /url-checkers - /view-helper - /migration-from-the-authcomponent - /upgrade-2-to-3 diff --git a/docs/en/identifiers.md b/docs/en/identifiers.md new file mode 100644 index 00000000..826a5371 --- /dev/null +++ b/docs/en/identifiers.md @@ -0,0 +1,219 @@ +# Identifiers + +Identifiers will identify an user or service based on the information +that was extracted from the request by the authenticators. Identifiers +can take options in the `loadIdentifier` method. A holistic example of +using the Password Identifier looks like: + +``` php +$identifier = [ + 'Authentication.Password' => [ + 'fields' => [ + 'username' => 'email', + 'password' => 'passwd', + ], + 'resolver' => [ + 'className' => 'Authentication.Orm', + 'userModel' => 'Users', + 'finder' => 'active', // default: 'all' + ], + 'passwordHasher' => [ + 'className' => 'Authentication.Fallback', + 'hashers' => [ + 'Authentication.Default' => [ + 'className' => 'Authentication.Legacy', + 'hashType' => 'md5', + ], + ], + ], + ], +]; +``` + +## Password + +The password identifier checks the passed credentials against a +datasource. + +Configuration options: + +- **fields**: The fields for the lookup. Default is + `['username' => 'username', 'password' => 'password']`. You can + also set the `username` to an array. For e.g. using + `['username' => ['username', 'email'], 'password' => 'password']` + will allow you to match value of either username or email columns. +- **resolver**: The identity resolver. Default is + `Authentication.Orm` which uses CakePHP ORM. +- **passwordHasher**: Password hasher. Default is + `DefaultPasswordHasher::class`. + +## Token + +Checks the passed token against a datasource. + +Configuration options: + +- **tokenField**: The field in the database to check against. Default + is `token`. +- **dataField**: The field in the passed data from the authenticator. + Default is `token`. +- **resolver**: The identity resolver. Default is + `Authentication.Orm` which uses CakePHP ORM. +- **hashAlgorithm**: The algorithm used to hash the incoming token + with before comparing it to the `tokenField`. Recommended value is + `sha256`. Default is `null`. + +## JWT Subject + +Checks the passed JWT token against a datasource. + +Configuration options: + +- **tokenField**: The field in the database to check against. Default + is `id`. +- **dataField**: The payload key to get user identifier from. Default + is `sub`. +- **resolver**: The identity resolver. Default is + `Authentication.Orm` which uses CakePHP ORM. + +## LDAP + +Checks the passed credentials against a LDAP server. This identifier +requires the PHP LDAP extension. + +Configuration options: + +- **fields**: The fields for the lookup. Default is + `['username' => 'username', 'password' => 'password']`. + +- **host**: The FQDN of your LDAP server. + +- **port**: The port of your LDAP server. Defaults to `389`. + +- **bindDN**: The Distinguished Name of the user to authenticate. Must + be a callable. Anonymous binds are not supported. + +- **ldap**: The extension adapter. Defaults to + `\Authentication\Identifier\Ldap\ExtensionAdapter`. You can pass a + custom object/classname here if it implements the + `AdapterInterface`. + +- **options**: Array of additional LDAP options, including + `tls`: Boolean. If `true`, tries to start TLS on the connection. + Also LDAP config options such as + `LDAP_OPT_PROTOCOL_VERSION` or `LDAP_OPT_NETWORK_TIMEOUT`. See + + [php.net](https://php.net/manual/en/function.ldap-set-option.php) + for more valid options. + +## Callback + +Allows you to use a callback for identification. This is useful for +simple identifiers or quick prototyping. + +Configuration options: + +- **callback**: Default is `null` and will cause an exception. You’re + required to pass a valid callback to this option to use the + authenticator. + +Callback identifiers can either return `null|ArrayAccess` for simple results, or an `Authentication\Authenticator\Result` if you want to forward error messages: + +``` php +// A simple callback identifier +$identifier = [ + 'Authentication.Callback' => [ + 'callback' => function($data) { + // do identifier logic + + // Return an array of the identified user or null for failure. + if ($result) { + return $result; + } + + return null; + }, + ] +]; + +// Using a result object to return error messages. +$identifier = [ + 'Authentication.Callback' => [ + 'callback' => function($data) { + // do identifier logic + + if ($result) { + return new Result($result, Result::SUCCESS); + } + + return new Result( + null, + Result::FAILURE_OTHER, + ['message' => 'Removed user.'] + ); + }, + ], +]; +``` + +## Identity resolvers + +Identity resolvers provide adapters for different datasources. They +allow you to control which source identities are searched in. They are +separate from the identifiers so that they can be swapped out +independently from the identifier method (form, jwt, basic auth). + +### ORM Resolver + +Identity resolver for the CakePHP ORM. + +Configuration options: + +- **userModel**: The user model identities are located in. Default is + `Users`. +- **finder**: The finder to use with the model. Default is `all`. + You can read more about model finders [here](https://book.cakephp.org/5/en/orm/retrieving-data-and-resultsets.html#custom-finder-methods). + +In order to use ORM resolver you must require `cakephp/orm` in your +`composer.json` file (if you are not already using the full CakePHP framework). + +### Writing your own resolver + +Any ORM or datasource can be adapted to work with authentication by +creating a resolver. Resolvers must implement +`Authentication\Identifier\Resolver\ResolverInterface` and should +reside under `App\Identifier\Resolver` namespace. + +Resolver can be configured using `resolver` config option: + +``` php +$identifier = [ + 'Authentication.Password' => [ + 'resolver' => [ + // can be a full class name: \Some\Other\Custom\Resolver::class + 'className' => 'MyResolver', + // Pass additional options to the resolver constructor. + 'option' => 'value', + ], + ], +]; +``` + +Or injected using a setter: + +``` php +$resolver = new \App\Identifier\Resolver\CustomResolver(); +$identifier = $service->loadIdentifier('Authentication.Password'); +$identifier->setResolver($resolver); +``` + +As of 3.3.0, you should pass the constructed resolver into the identifier: + +``` php +$resolver = new \App\Identifier\Resolver\CustomResolver(); +$identifier = [ + 'Authentication.Password' => [ + 'resolver' => $resolver, + ], +]; +``` diff --git a/docs/en/identifiers.rst b/docs/en/identifiers.rst deleted file mode 100644 index 9db0e9aa..00000000 --- a/docs/en/identifiers.rst +++ /dev/null @@ -1,213 +0,0 @@ -Identifiers -########### - -Identifiers will identify an user or service based on the information -that was extracted from the request by the authenticators. Identifiers -can take options in the ``loadIdentifier`` method. A holistic example of -using the Password Identifier looks like:: - - $identifier = [ - 'Authentication.Password' => [ - 'fields' => [ - 'username' => 'email', - 'password' => 'passwd', - ], - 'resolver' => [ - 'className' => 'Authentication.Orm', - 'userModel' => 'Users', - 'finder' => 'active', // default: 'all' - ], - 'passwordHasher' => [ - 'className' => 'Authentication.Fallback', - 'hashers' => [ - 'Authentication.Default' => [ - 'className' => 'Authentication.Legacy', - 'hashType' => 'md5', - ], - ], - ], - ], - ]; - -Password -======== - -The password identifier checks the passed credentials against a -datasource. - -Configuration options: - -- **fields**: The fields for the lookup. Default is - ``['username' => 'username', 'password' => 'password']``. You can - also set the ``username`` to an array. For e.g. using - ``['username' => ['username', 'email'], 'password' => 'password']`` - will allow you to match value of either username or email columns. -- **resolver**: The identity resolver. Default is - ``Authentication.Orm`` which uses CakePHP ORM. -- **passwordHasher**: Password hasher. Default is - ``DefaultPasswordHasher::class``. - -Token -===== - -Checks the passed token against a datasource. - -Configuration options: - -- **tokenField**: The field in the database to check against. Default - is ``token``. -- **dataField**: The field in the passed data from the authenticator. - Default is ``token``. -- **resolver**: The identity resolver. Default is - ``Authentication.Orm`` which uses CakePHP ORM. -- **hashAlgorithm**: The algorithm used to hash the incoming token - with before comparing it to the ``tokenField``. Recommended value is - ``sha256``. Default is ``null``. - -JWT Subject -=========== - -Checks the passed JWT token against a datasource. - -Configuration options: - -- **tokenField**: The field in the database to check against. Default - is ``id``. -- **dataField**: The payload key to get user identifier from. Default - is ``sub``. -- **resolver**: The identity resolver. Default is - ``Authentication.Orm`` which uses CakePHP ORM. - -LDAP -==== - -Checks the passed credentials against a LDAP server. This identifier -requires the PHP LDAP extension. - -Configuration options: - -- **fields**: The fields for the lookup. Default is - ``['username' => 'username', 'password' => 'password']``. -- **host**: The FQDN of your LDAP server. -- **port**: The port of your LDAP server. Defaults to ``389``. -- **bindDN**: The Distinguished Name of the user to authenticate. Must - be a callable. Anonymous binds are not supported. -- **ldap**: The extension adapter. Defaults to - ``\Authentication\Identifier\Ldap\ExtensionAdapter``. You can pass a - custom object/classname here if it implements the - ``AdapterInterface``. -- **options**: Array of additional LDAP options, including - ``tls``: Boolean. If ``true``, tries to start TLS on the connection. - Also LDAP config options such as - ``LDAP_OPT_PROTOCOL_VERSION`` or ``LDAP_OPT_NETWORK_TIMEOUT``. See - `php.net `__ - for more valid options. - -Callback -======== - -Allows you to use a callback for identification. This is useful for -simple identifiers or quick prototyping. - -Configuration options: - -- **callback**: Default is ``null`` and will cause an exception. You’re - required to pass a valid callback to this option to use the - authenticator. - -Callback identifiers can either return ``null|ArrayAccess`` for simple results, -or an ``Authentication\Authenticator\Result`` if you want to forward error -messages:: - - // A simple callback identifier - $identifier = [ - 'Authentication.Callback' => [ - 'callback' => function($data) { - // do identifier logic - - // Return an array of the identified user or null for failure. - if ($result) { - return $result; - } - - return null; - }, - ] - ]; - - // Using a result object to return error messages. - $identifier = [ - 'Authentication.Callback' => [ - 'callback' => function($data) { - // do identifier logic - - if ($result) { - return new Result($result, Result::SUCCESS); - } - - return new Result( - null, - Result::FAILURE_OTHER, - ['message' => 'Removed user.'] - ); - }, - ]; - ]; - -Identity resolvers -================== - -Identity resolvers provide adapters for different datasources. They -allow you to control which source identities are searched in. They are -separate from the identifiers so that they can be swapped out -independently from the identifier method (form, jwt, basic auth). - -ORM Resolver ------------- - -Identity resolver for the CakePHP ORM. - -Configuration options: - -- **userModel**: The user model identities are located in. Default is - ``Users``. -- **finder**: The finder to use with the model. Default is ``all``. - You can read more about model finders `here `__. - -In order to use ORM resolver you must require ``cakephp/orm`` in your -``composer.json`` file (if you are not already using the full CakePHP framework). - -Writing your own resolver -------------------------- - -Any ORM or datasource can be adapted to work with authentication by -creating a resolver. Resolvers must implement -``Authentication\Identifier\Resolver\ResolverInterface`` and should -reside under ``App\Identifier\Resolver`` namespace. - -Resolver can be configured using ``resolver`` config option:: - - $identifier = [ - 'Authentication.Password' => [ - 'resolver' => [ - // can be a full class name: \Some\Other\Custom\Resolver::class - 'className' => 'MyResolver', - // Pass additional options to the resolver constructor. - 'option' => 'value', - ], - ]; - ]; - -Or injected using a setter:: - - $resolver = new \App\Identifier\Resolver\CustomResolver(); - $identifier = $service->loadIdentifier('Authentication.Password'); - $identifier->setResolver($resolver); - -As of 3.3.0, you should pass the constructed resolver into the identifier:: - - $resolver = new \App\Identifier\Resolver\CustomResolver(); - $identifier = [ - 'Authentication.Password' => [ - 'resolver' => $resolver; - ]; diff --git a/docs/en/identity-object.md b/docs/en/identity-object.md new file mode 100644 index 00000000..88b89439 --- /dev/null +++ b/docs/en/identity-object.md @@ -0,0 +1,124 @@ +# Identity Objects + +Identity objects are returned by the authentication service and made available +in the request. Identities provides a method `getIdentifier()` that can be +called to get the primary id value of the current log in identity. + +The reason this object exists is to provide an interface that makes it +implementations/sources: + +``` php +// Service +$authenticationService + ->getIdentity() + ->getIdentifier() + +// Component +$this->Authentication + ->getIdentity() + ->getIdentifier(); + +// Request +$this->request + ->getAttribute('identity') + ->getIdentifier(); +``` + +The identity object provides ArrayAccess but as well a `get()` method to +access data. It is strongly recommended to use the `get()` method over array +access because the get method is aware of the field mapping: + +``` php +$identity->get('email'); +$identity->get('username'); +``` + +The `get()` method can also be type-hinted via IDE meta file, e.g. through +[IdeHelper](https://github.com/dereuromark/cakephp-ide-helper). + +If you want, you can use property access, however: + +``` text +$identity->email; +$identity->username; +``` + +The default Identity object class can be configured to map fields. This +is pretty useful if the identifier of the identity is a non-conventional +`id` field or if you want to map other fields to more generic and +common names: + +``` php +$identity = new Identity($data, [ + 'fieldMap' => [ + 'id' => 'uid', + 'username' => 'first_name' + ] +]); +``` + +## Creating your own Identity Object + +By default the Authentication plugin will wrap your returned user data in an +`IdentityDecorator` that proxies methods and property access. If you want to +create your own identity object, your object must implement the +`IdentityInterface`. + +## Implementing the IdentityInterface on your User class + +If you’d like to continue using your existing User class with this +plugin you can implement the `Authentication\IdentityInterface`: + +``` php +namespace App\Model\Entity; + +use Authentication\IdentityInterface; +use Cake\ORM\Entity; + +class User extends Entity implements IdentityInterface +{ + /** + * Authentication\IdentityInterface method + */ + public function getIdentifier() + { + return $this->id; + } + + /** + * Authentication\IdentityInterface method + */ + public function getOriginalData() + { + return $this; + } + + // Other methods +} +``` + +## Using a Custom Identity Decorator + +If your identifiers cannot have their resulting objects modified to +implement the `IdentityInterface` you can implement a custom decorator +that implements the required interface: + +``` php +// You can use a callable... +$identityResolver = function ($data) { + return new MyCustomIdentity($data); +}; + +//...or a class name to set the identity wrapper. +$identityResolver = MyCustomIdentity::class; + +// Then pass it to the service configuration +$service = new AuthenticationService([ + 'identityClass' => $identityResolver, + 'authenticators' => [ + 'Authentication.Form' => [ + 'identifier' => 'Authentication.Password', + ], + ] +]); +``` diff --git a/docs/en/identity-object.rst b/docs/en/identity-object.rst deleted file mode 100644 index f606ea22..00000000 --- a/docs/en/identity-object.rst +++ /dev/null @@ -1,116 +0,0 @@ -Identity Objects -################ - -Identity objects are returned by the authentication service and made available -in the request. Identities provides a method ``getIdentifier()`` that can be -called to get the primary id value of the current log in identity. - -The reason this object exists is to provide an interface that makes it -implementations/sources:: - - // Service - $authenticationService - ->getIdentity() - ->getIdentifier() - - // Component - $this->Authentication - ->getIdentity() - ->getIdentifier(); - - // Request - $this->request - ->getAttribute('identity') - ->getIdentifier(); - -The identity object provides ArrayAccess but as well a ``get()`` method to -access data. It is strongly recommended to use the ``get()`` method over array -access because the get method is aware of the field mapping:: - - $identity->get('email'); - $identity->get('username'); - -The ``get()`` method can also be type-hinted via IDE meta file, e.g. through -`IdeHelper `__. - -If you want, you can use property access, however:: - - $identity->email; - $identity->username; - -The default Identity object class can be configured to map fields. This -is pretty useful if the identifier of the identity is a non-conventional -``id`` field or if you want to map other fields to more generic and -common names:: - - $identity = new Identity($data, [ - 'fieldMap' => [ - 'id' => 'uid', - 'username' => 'first_name' - ] - ]); - -Creating your own Identity Object ---------------------------------- - -By default the Authentication plugin will wrap your returned user data in an -``IdentityDecorator`` that proxies methods and property access. If you want to -create your own identity object, your object must implement the -``IdentityInterface``. - -Implementing the IdentityInterface on your User class ------------------------------------------------------ - -If you’d like to continue using your existing User class with this -plugin you can implement the ``Authentication\IdentityInterface``:: - - namespace App\Model\Entity; - - use Authentication\IdentityInterface; - use Cake\ORM\Entity; - - class User extends Entity implements IdentityInterface - { - /** - * Authentication\IdentityInterface method - */ - public function getIdentifier() - { - return $this->id; - } - - /** - * Authentication\IdentityInterface method - */ - public function getOriginalData() - { - return $this; - } - - // Other methods - } - -Using a Custom Identity Decorator ---------------------------------- - -If your identifiers cannot have their resulting objects modified to -implement the ``IdentityInterface`` you can implement a custom decorator -that implements the required interface:: - - // You can use a callable... - $identityResolver = function ($data) { - return new MyCustomIdentity($data); - }; - - //...or a class name to set the identity wrapper. - $identityResolver = MyCustomIdentity::class; - - // Then pass it to the service configuration - $service = new AuthenticationService([ - 'identityClass' => $identityResolver, - 'authenticators' => [ - 'Authentication.Form' => [ - 'identifier' => 'Authentication.Password', - ], - ] - ]); diff --git a/docs/en/impersonation.md b/docs/en/impersonation.md new file mode 100644 index 00000000..1e59dace --- /dev/null +++ b/docs/en/impersonation.md @@ -0,0 +1,71 @@ +# User Impersonation + +::: info Added in version 2.10.0 +User impersonation was added. +::: + +After deploying your application, you may occasionally need to +'impersonate' another user in order to debug problems that your customers report +or to see the application in the state that your customers are seeing it. + +## Enabling Impersonation + +To impersonate another user you can use the `impersonate()` method on the +`AuthenticationComponent`. To impersonate a user you first need to load that +user from your application's database: + +``` php +// In a controller +public function impersonate() +{ + $this->request->allowMethod(['POST']); + + $currentUser = $this->request->getAttribute('identity'); + + // You should always check that the current user is allowed + // to impersonate other users first. + if (!$currentUser->isStaff()) { + throw new NotFoundException(); + } + + // Fetch the user we want to impersonate. + $targetUser = $this->Users->findById( + $this->request->getData('user_id') + )->firstOrFail(); + + // Enable impersonation. + $this->Authentication->impersonate($targetUser); + + return $this->redirect($this->referer()); +} +``` + +Once you have started to impersonate a user, all subsequent requests will have +`$targetUser` as the active identity. + +## Ending Impersonation + +Once you are done impersonating a user, you can then end impersonation and revert +back to your previous identity using `AuthenticationComponent`: + +``` php +// In a controller +public function revertIdentity() +{ + $this->request->allowMethod(['POST']); + + // Make sure we are still impersonating a user. + if (!$this->Authentication->isImpersonating()) { + throw new NotFoundException(); + } + $this->Authentication->stopImpersonating(); +} +``` + +## Limitations to Impersonation + +There are a few limitations to impersonation. + +1. Your application must be using the `Session` authenticator. +2. You cannot impersonate another user while impersonation is active. Instead + you must `stopImpersonation()` and then start it again. diff --git a/docs/en/impersonation.rst b/docs/en/impersonation.rst deleted file mode 100644 index e4caf7a7..00000000 --- a/docs/en/impersonation.rst +++ /dev/null @@ -1,70 +0,0 @@ -User Impersonation -################## - -.. versionadded:: 2.10.0 - User impersonation was added. - -After deploying your application, you may occasionally need to -'impersonate' another user in order to debug problems that your customers report -or to see the application in the state that your customers are seeing it. - -Enabling Impersonation -====================== - -To impersonate another user you can use the ``impersonate()`` method on the -``AuthenticationComponent``. To impersonate a user you first need to load that -user from your application's database:: - - // In a controller - public function impersonate() - { - $this->request->allowMethod(['POST']); - - $currentUser = $this->request->getAttribute('identity'); - - // You should always check that the current user is allowed - // to impersonate other users first. - if (!$currentUser->isStaff()) { - throw new NotFoundException(); - } - - // Fetch the user we want to impersonate. - $targetUser = $this->Users->findById( - $this->request->getData('user_id') - )->firstOrFail(); - - // Enable impersonation. - $this->Authentication->impersonate($targetUser); - - return $this->redirect($this->referer()); - } - -Once you have started to impersonate a user, all subsequent requests will have -``$targetUser`` as the active identity. - -Ending Impersonation -==================== - -Once you are done impersonating a user, you can then end impersonation and revert -back to your previous identity using ``AuthenticationComponent``:: - - // In a controller - public function revertIdentity() - { - $this->request->allowMethod(['POST']); - - // Make sure we are still impersonating a user. - if (!$this->Authentication->isImpersonating()) { - throw new NotFoundException(); - } - $this->Authentication->stopImpersonating(); - } - -Limitations to Impersonation -============================ - -There are a few limitations to impersonation. - -#. Your application must be using the ``Session`` authenticator. -#. You cannot impersonate another user while impersonation is active. Instead - you must ``stopImpersonation()`` and then start it again. diff --git a/docs/en/index.md b/docs/en/index.md new file mode 100644 index 00000000..6e603545 --- /dev/null +++ b/docs/en/index.md @@ -0,0 +1,266 @@ +# Quick Start + +Install the plugin with [composer](https://getcomposer.org/) from your CakePHP +Project's ROOT directory (where the **composer.json** file is located) + +``` bash +php composer.phar require cakephp/authentication +``` + +Version 4 of the Authentication Plugin is compatible with CakePHP 5. + +Load the plugin using the following command: + +``` shell +bin/cake plugin load Authentication +``` + +## Getting Started + +The authentication plugin integrates with your application as a [middleware](https://book.cakephp.org/5/en/controllers/middleware.html). It can also +be used as a component to make unauthenticated access simpler. First, let's +apply the middleware. In **src/Application.php**, add the following to the class +imports: + +``` php +use Authentication\AuthenticationService; +use Authentication\AuthenticationServiceInterface; +use Authentication\AuthenticationServiceProviderInterface; +use Authentication\Identifier\AbstractIdentifier; +use Authentication\Middleware\AuthenticationMiddleware; +use Cake\Http\MiddlewareQueue; +use Cake\Routing\Router; +use Psr\Http\Message\ServerRequestInterface; +``` + +Next, add `AuthenticationServiceProviderInterface` to the implemented interfaces +on your application: + + class Application extends BaseApplication implements AuthenticationServiceProviderInterface + +Then update your application's `middleware()` method to look like: + +``` php +public function middleware(MiddlewareQueue $middlewareQueue): MiddlewareQueue +{ + $middlewareQueue->add(new ErrorHandlerMiddleware(Configure::read('Error'))) + // Other middleware that CakePHP provides. + ->add(new AssetMiddleware()) + ->add(new RoutingMiddleware($this)) + ->add(new BodyParserMiddleware()) + + // Add the AuthenticationMiddleware. It should be + // after routing and body parser. + ->add(new AuthenticationMiddleware($this)); + + return $middlewareQueue; +} +``` + +> [!WARNING] +> The order of middleware is important. Ensure that you have +> `AuthenticationMiddleware` after the routing and body parser middleware. +> If you're having trouble logging in with JSON requests or redirects are +> incorrect double check your middleware order. + +`AuthenticationMiddleware` will call a hook method on your application when +it starts handling the request. This hook method allows your application to +define the `AuthenticationService` it wants to use. Add the following method to your +**src/Application.php**: + +``` php +/** + * Returns a service provider instance. + * + * @param \Psr\Http\Message\ServerRequestInterface $request Request + * @return \Authentication\AuthenticationServiceInterface + */ +public function getAuthenticationService(ServerRequestInterface $request): AuthenticationServiceInterface +{ + $service = new AuthenticationService(); + + // Define where users should be redirected to when they are not authenticated + $service->setConfig([ + 'unauthenticatedRedirect' => [ + 'prefix' => false, + 'plugin' => false, + 'controller' => 'Users', + 'action' => 'login', + ], + 'queryParam' => 'redirect', + ]); + + $fields = [ + AbstractIdentifier::CREDENTIAL_USERNAME => 'email', + AbstractIdentifier::CREDENTIAL_PASSWORD => 'password', + ]; + + // Load the authenticators. Session should be first. + // Session just uses session data directly as identity, no identifier needed. + $service->loadAuthenticator('Authentication.Session'); + $service->loadAuthenticator('Authentication.Form', [ + 'identifier' => [ + 'Authentication.Password' => [ + 'fields' => $fields, + ], + ], + 'fields' => $fields, + 'loginUrl' => Router::url([ + 'prefix' => false, + 'plugin' => null, + 'controller' => 'Users', + 'action' => 'login', + ]), + ]); + + return $service; +} +``` + +First, we configure what to do with users when they are not authenticated. +Next, we attach the `Session` and `Form` [Authenticators](authenticators) which define the +mechanisms that our application will use to authenticate users. `Session` enables us to identify +users based on data in the session - it uses the session data directly as identity without any +database lookup. `Form` enables us to handle a login form at the `loginUrl` and uses an +[identifier](identifiers) to convert the credentials users will give us into an +[identity](identity-object) which represents our logged in user. + +If one of the configured authenticators was able to validate the credentials, +the middleware will add the authentication service to the request object as an +[attribute](https://www.php-fig.org/psr/psr-7/). + +Next, in your `AppController` load the [Authentication Component](authentication-component): + +``` php +// in src/Controller/AppController.php +public function initialize() +{ + parent::initialize(); + + $this->loadComponent('Authentication.Authentication'); +} +``` + +By default the component will require an authenticated user for **all** actions. +You can disable this behavior in specific controllers using +`allowUnauthenticated()`: + +``` php +// in a controller beforeFilter or initialize +// Make view and index not require a logged in user. +$this->Authentication->allowUnauthenticated(['view', 'index']); +``` + +## Building a Login Action + +Once you have the middleware applied to your application you'll need a way for +users to login. Please ensure your database has been created with the Users table structure used in [tutorial](tutorials-and-examples/cms/database). First generate a Users model and controller with bake: + +``` bash +bin/cake bake model Users +bin/cake bake controller Users +``` + +Then, we'll add a basic login action to your `UsersController`. It should look +like: + +``` php +// in src/Controller/UsersController.php +public function login() +{ + $result = $this->Authentication->getResult(); + // If the user is logged in send them away. + if ($result && $result->isValid()) { + $target = $this->Authentication->getLoginRedirect() ?? '/home'; + return $this->redirect($target); + } + if ($this->request->is('post')) { + $this->Flash->error('Invalid username or password'); + } +} +``` + +Make sure that you allow access to the `login` action in your controller's +`beforeFilter()` callback as mentioned in the previous section, so that +unauthenticated users are able to access it: + +``` php +// in src/Controller/UsersController.php +public function beforeFilter(\Cake\Event\EventInterface $event) +{ + parent::beforeFilter($event); + + $this->Authentication->allowUnauthenticated(['login']); +} +``` + +Next we'll add a view template for our login form: + +``` php +// in templates/Users/login.php +
+ Form->create() ?> +
+ + Form->control('email') ?> + Form->control('password') ?> +
+ Form->button(__('Login')) ?> + Form->end() ?> +
+``` + +Then add a simple logout action: + +``` php +// in src/Controller/UsersController.php +public function logout() +{ + $this->Authentication->logout(); + return $this->redirect(['controller' => 'Users', 'action' => 'login']); +} +``` + +We don't need a template for our logout action as we redirect at the end of it. + +## Adding Password Hashing + +In order to login your users will need to have hashed passwords. You can +automatically hash passwords when users update their password using an entity +setter method: + +``` php +// in src/Model/Entity/User.php +use Authentication\PasswordHasher\DefaultPasswordHasher; + +class User extends Entity +{ + // ... other methods + + // Automatically hash passwords when they are changed. + protected function _setPassword(string $password) + { + $hasher = new DefaultPasswordHasher(); + return $hasher->hash($password); + } +} +``` + +You should now be able to go to `/users/add` and register a new user. Once +registered you can go to `/users/login` and login with your newly created +user. + +## Further Reading + +- [Authenticators](authenticators) +- [Identifiers](identifiers) +- [Password Hashers](password-hashers) +- [Identity Objects](identity-object) +- [Middleware](middleware) +- [Authentication Component](authentication-component) +- [User Impersonation](impersonation) +- [URL Checkers](url-checkers) +- [Redirect Validation](redirect-validation) +- [Testing with Authentication](testing) +- [View Helper](view-helper) +- [Migration from the AuthComponent](migration-from-the-authcomponent) diff --git a/docs/en/index.rst b/docs/en/index.rst deleted file mode 100644 index 36de148f..00000000 --- a/docs/en/index.rst +++ /dev/null @@ -1,254 +0,0 @@ -Quick Start -########### - -Install the plugin with `composer `_ from your CakePHP -Project's ROOT directory (where the **composer.json** file is located) - -.. code-block:: bash - - php composer.phar require cakephp/authentication - -Version 4 of the Authentication Plugin is compatible with CakePHP 5. - -Load the plugin using the following command:: - -.. code-block:: shell - - bin/cake plugin load Authentication - -Getting Started -=============== - -The authentication plugin integrates with your application as a `middleware `_. It can also -be used as a component to make unauthenticated access simpler. First, let's -apply the middleware. In **src/Application.php**, add the following to the class -imports:: - - use Authentication\AuthenticationService; - use Authentication\AuthenticationServiceInterface; - use Authentication\AuthenticationServiceProviderInterface; - use Authentication\Identifier\AbstractIdentifier; - use Authentication\Middleware\AuthenticationMiddleware; - use Cake\Http\MiddlewareQueue; - use Cake\Routing\Router; - use Psr\Http\Message\ServerRequestInterface; - - -Next, add ``AuthenticationServiceProviderInterface`` to the implemented interfaces -on your application:: - - class Application extends BaseApplication implements AuthenticationServiceProviderInterface - - -Then update your application's ``middleware()`` method to look like:: - - public function middleware(MiddlewareQueue $middlewareQueue): MiddlewareQueue - { - $middlewareQueue->add(new ErrorHandlerMiddleware(Configure::read('Error'))) - // Other middleware that CakePHP provides. - ->add(new AssetMiddleware()) - ->add(new RoutingMiddleware($this)) - ->add(new BodyParserMiddleware()) - - // Add the AuthenticationMiddleware. It should be - // after routing and body parser. - ->add(new AuthenticationMiddleware($this)); - - return $middlewareQueue; - } - -.. warning:: - The order of middleware is important. Ensure that you have - ``AuthenticationMiddleware`` after the routing and body parser middleware. - If you're having trouble logging in with JSON requests or redirects are - incorrect double check your middleware order. - -``AuthenticationMiddleware`` will call a hook method on your application when -it starts handling the request. This hook method allows your application to -define the ``AuthenticationService`` it wants to use. Add the following method to your -**src/Application.php**:: - - /** - * Returns a service provider instance. - * - * @param \Psr\Http\Message\ServerRequestInterface $request Request - * @return \Authentication\AuthenticationServiceInterface - */ - public function getAuthenticationService(ServerRequestInterface $request): AuthenticationServiceInterface - { - $service = new AuthenticationService(); - - // Define where users should be redirected to when they are not authenticated - $service->setConfig([ - 'unauthenticatedRedirect' => [ - 'prefix' => false, - 'plugin' => false, - 'controller' => 'Users', - 'action' => 'login', - ], - 'queryParam' => 'redirect', - ]); - - $fields = [ - AbstractIdentifier::CREDENTIAL_USERNAME => 'email', - AbstractIdentifier::CREDENTIAL_PASSWORD => 'password', - ]; - - // Load the authenticators. Session should be first. - // Session just uses session data directly as identity, no identifier needed. - $service->loadAuthenticator('Authentication.Session'); - $service->loadAuthenticator('Authentication.Form', [ - 'identifier' => [ - 'Authentication.Password' => [ - 'fields' => $fields, - ], - ], - 'fields' => $fields, - 'loginUrl' => Router::url([ - 'prefix' => false, - 'plugin' => null, - 'controller' => 'Users', - 'action' => 'login', - ]), - ]); - - return $service; - } - -First, we configure what to do with users when they are not authenticated. -Next, we attach the ``Session`` and ``Form`` :doc:`/authenticators` which define the -mechanisms that our application will use to authenticate users. ``Session`` enables us to identify -users based on data in the session - it uses the session data directly as identity without any -database lookup. ``Form`` enables us to handle a login form at the ``loginUrl`` and uses an -:doc:`identifier ` to convert the credentials users will give us into an -:doc:`identity ` which represents our logged in user. - -If one of the configured authenticators was able to validate the credentials, -the middleware will add the authentication service to the request object as an -`attribute `_. - -Next, in your ``AppController`` load the :doc:`/authentication-component`:: - - // in src/Controller/AppController.php - public function initialize() - { - parent::initialize(); - - $this->loadComponent('Authentication.Authentication'); - } - -By default the component will require an authenticated user for **all** actions. -You can disable this behavior in specific controllers using -``allowUnauthenticated()``:: - - // in a controller beforeFilter or initialize - // Make view and index not require a logged in user. - $this->Authentication->allowUnauthenticated(['view', 'index']); - -Building a Login Action -======================= - -Once you have the middleware applied to your application you'll need a way for -users to login. Please ensure your database has been created with the Users table structure used in :doc:`tutorial `. First generate a Users model and controller with bake: - -.. code-block:: shell - - bin/cake bake model Users - bin/cake bake controller Users - -Then, we'll add a basic login action to your ``UsersController``. It should look -like:: - - // in src/Controller/UsersController.php - public function login() - { - $result = $this->Authentication->getResult(); - // If the user is logged in send them away. - if ($result && $result->isValid()) { - $target = $this->Authentication->getLoginRedirect() ?? '/home'; - return $this->redirect($target); - } - if ($this->request->is('post')) { - $this->Flash->error('Invalid username or password'); - } - } - -Make sure that you allow access to the ``login`` action in your controller's -``beforeFilter()`` callback as mentioned in the previous section, so that -unauthenticated users are able to access it:: - - // in src/Controller/UsersController.php - public function beforeFilter(\Cake\Event\EventInterface $event) - { - parent::beforeFilter($event); - - $this->Authentication->allowUnauthenticated(['login']); - } - -Next we'll add a view template for our login form:: - - // in templates/Users/login.php -
- Form->create() ?> -
- - Form->control('email') ?> - Form->control('password') ?> -
- Form->button(__('Login')) ?> - Form->end() ?> -
- -Then add a simple logout action:: - - // in src/Controller/UsersController.php - public function logout() - { - $this->Authentication->logout(); - return $this->redirect(['controller' => 'Users', 'action' => 'login']); - } - -We don't need a template for our logout action as we redirect at the end of it. - -Adding Password Hashing -======================= - -In order to login your users will need to have hashed passwords. You can -automatically hash passwords when users update their password using an entity -setter method:: - - // in src/Model/Entity/User.php - use Authentication\PasswordHasher\DefaultPasswordHasher; - - class User extends Entity - { - // ... other methods - - // Automatically hash passwords when they are changed. - protected function _setPassword(string $password) - { - $hasher = new DefaultPasswordHasher(); - return $hasher->hash($password); - } - } - -You should now be able to go to ``/users/add`` and register a new user. Once -registered you can go to ``/users/login`` and login with your newly created -user. - - -Further Reading -=============== - -* :doc:`/authenticators` -* :doc:`/identifiers` -* :doc:`/password-hashers` -* :doc:`/identity-object` -* :doc:`/middleware` -* :doc:`/authentication-component` -* :doc:`/impersonation` -* :doc:`/url-checkers` -* :doc:`/redirect-validation` -* :doc:`/testing` -* :doc:`/view-helper` -* :doc:`/migration-from-the-authcomponent` diff --git a/docs/en/middleware.md b/docs/en/middleware.md new file mode 100644 index 00000000..80769dde --- /dev/null +++ b/docs/en/middleware.md @@ -0,0 +1,63 @@ +# Middleware + +`AuthenticationMiddleware` forms the heart of the authentication plugin. +It intercepts each request to your application and attempts to authenticate +a user with one of the authenticators. Each authenticator is tried in order +until a user is authenticated or no user could be found. The `authentication`, +`identity` and `authenticationResult` attributes are set on the request +containing the identity if one was found and the authentication result object +which can contain additional errors provided by the authenticators. + +At the end of each request the `identity` is persisted into each stateful +authenticator, like the `Session` authenticator. + +## Configuration + +All configuration for the middleware is done on the `AuthenticationService`. +On the service you can use the following configuration options: + +- `identityClass` - The class name of identity or a callable identity builder. +- `identityAttribute` - The request attribute used to store the identity. + Default to `identity`. +- `unauthenticatedRedirect` - The URL to redirect unauthenticated errors to. +- `queryParam` - The name of the query string parameter containing the + previously blocked URL in case of unauthenticated redirect, or null to disable + appending the denied URL. Defaults to `null`. + +## Configuring Multiple Authentication Setups + +If your application requires different authentication setups for different parts +of the application for example the API and Web UI. You can do so by using conditional +logic in your applications `getAuthenticationService()` hook method. By +inspecting the request object you can configure authentication appropriately: + +``` php +public function getAuthenticationService(ServerRequestInterface $request): AuthenticationServiceInterface +{ + $path = $request->getPath(); + + $service = new AuthenticationService(); + if (strpos($path, '/api') === 0) { + // Accept API tokens only + $service->loadAuthenticator('Authentication.Token', [ + 'identifier' => 'Authentication.Token', + ]); + + return $service; + } + + // Web authentication + // Support sessions and form login. + $service->loadAuthenticator('Authentication.Session', [ + 'identifier' => 'Authentication.Password', + ]); + $service->loadAuthenticator('Authentication.Form', [ + 'identifier' => 'Authentication.Password', + ]); + + return $service; +} +``` + +While the above example uses a path prefix, you could apply similar logic to the +subdomain, domain, or any other header or attribute present in the request. diff --git a/docs/en/middleware.rst b/docs/en/middleware.rst deleted file mode 100644 index 2e8cb70f..00000000 --- a/docs/en/middleware.rst +++ /dev/null @@ -1,65 +0,0 @@ -Middleware -########## - -``AuthenticationMiddleware`` forms the heart of the authentication plugin. -It intercepts each request to your application and attempts to authenticate -a user with one of the authenticators. Each authenticator is tried in order -until a user is authenticated or no user could be found. The ``authentication``, -``identity`` and ``authenticationResult`` attributes are set on the request -containing the identity if one was found and the authentication result object -which can contain additional errors provided by the authenticators. - -At the end of each request the ``identity`` is persisted into each stateful -authenticator, like the ``Session`` authenticator. - -Configuration -============= - -All configuration for the middleware is done on the ``AuthenticationService``. -On the service you can use the following configuration options: - -- ``identityClass`` - The class name of identity or a callable identity builder. -- ``identityAttribute`` - The request attribute used to store the identity. - Default to ``identity``. -- ``unauthenticatedRedirect`` - The URL to redirect unauthenticated errors to. -- ``queryParam`` - The name of the query string parameter containing the - previously blocked URL in case of unauthenticated redirect, or null to disable - appending the denied URL. Defaults to ``null``. - - -Configuring Multiple Authentication Setups -========================================== - -If your application requires different authentication setups for different parts -of the application for example the API and Web UI. You can do so by using conditional -logic in your applications ``getAuthenticationService()`` hook method. By -inspecting the request object you can configure authentication appropriately:: - - public function getAuthenticationService(ServerRequestInterface $request): AuthenticationServiceInterface - { - $path = $request->getPath(); - - $service = new AuthenticationService(); - if (strpos($path, '/api') === 0) { - // Accept API tokens only - $service->loadAuthenticator('Authentication.Token', [ - 'identifier' => 'Authentication.Token', - ]); - - return $service; - } - - // Web authentication - // Support sessions and form login. - $service->loadAuthenticator('Authentication.Session', [ - 'identifier' => 'Authentication.Password', - ]); - $service->loadAuthenticator('Authentication.Form', [ - 'identifier' => 'Authentication.Password', - ]); - - return $service; - } - -While the above example uses a path prefix, you could apply similar logic to the -subdomain, domain, or any other header or attribute present in the request. diff --git a/docs/en/migration-from-the-authcomponent.md b/docs/en/migration-from-the-authcomponent.md new file mode 100644 index 00000000..06592823 --- /dev/null +++ b/docs/en/migration-from-the-authcomponent.md @@ -0,0 +1,346 @@ +# Migration from the AuthComponent + +## Differences + +- This plugin intentionally **does not** handle authorization. It was + [decoupled](https://en.wikipedia.org/wiki/Coupling_(computer_programming)) + from authorization on purpose for a clear [separation of + concerns](https://en.wikipedia.org/wiki/Separation_of_concerns). + See also [Computer access + control](https://en.wikipedia.org/wiki/Computer_access_control). + This plugin handles only *identification* and *authentication*. We + might have another plugin for authorization. +- There is no automatic checking of the session. To get the actual user + data from the session you’ll have to use the + `SessionAuthenticator`. It will check the session if there is data + in the configured session key and put it into the identity object. +- The user data is no longer available through the old AuthComponent but is + accessible via a request attribute and encapsulated in an identity + object: `$request->getAttribute('authentication')->getIdentity();`. + Additionally, you can leverage the `AuthenticationComponent` `getIdentity()` or `getIdentityData()` methods. +- The logic of the authentication process has been split into + authenticators and identifiers. An authenticator will extract the + credentials from the request, while identifiers verify the + credentials and find the matching user. +- DigestAuthenticate has been renamed to HttpDigestAuthenticator +- BasicAuthenticate has been renamed to HttpBasicAuthenticator + +## Similarities + +- All the existing authentication adapters, Form, Basic, Digest are + still there but have been refactored into authenticators. + +## Identifiers and authenticators + +Following the principle of separation of concerns, the former +authentication objects were split into separate objects, identifiers and +authenticators. + +- **Authenticators** take the incoming request and try to extract + identification credentials from it. If credentials are found, they + are passed to a collection of identifiers where the user is located. + For that reason authenticators take an IdentifierCollection as first + constructor argument. +- **Identifiers** verify identification credentials against a storage + system. eg. (ORM tables, LDAP etc) and return identified user data. + +This makes it easy to change the identification logic as needed or use +several sources of user data. + +If you want to implement your own identifiers, your identifier must +implement the `IdentifierInterface`. + +## Migrating your authentication setup + +The first step to migrating your application is to load the authentication +plugin in your application's bootstrap method: + +``` php +public function bootstrap(): void +{ + parent::bootstrap(); + $this->addPlugin('Authentication'); +} +``` + +Then update your application to implement the authentication provider interface. +This lets the AuthenticationMiddleware know how to get the authentication +service from your application: + +``` php +// in src/Application.php + +// Add the following use statements. +use Authentication\AuthenticationService; +use Authentication\AuthenticationServiceInterface; +use Authentication\AuthenticationServiceProviderInterface; +use Authentication\Middleware\AuthenticationMiddleware; +use Psr\Http\Message\ResponseInterface; +use Psr\Http\Message\ServerRequestInterface; + +// Add the authentication interface. +class Application extends BaseApplication implements AuthenticationServiceProviderInterface +{ + /** + * Returns a service provider instance. + * + * @param \Psr\Http\Message\ServerRequestInterface $request Request + * @param \Psr\Http\Message\ResponseInterface $response Response + * @return \Authentication\AuthenticationServiceInterface + */ + public function getAuthenticationService(ServerRequestInterface $request) : AuthenticationServiceInterface + { + $service = new AuthenticationService(); + // Configure the service. (see below for more details) + return $service; + } +} +``` + +Next add the `AuthenticationMiddleware` to your application: + +``` php +// in src/Application.php +public function middleware($middlewareQueue) +{ + // Various other middlewares for error handling, routing etc. added here. + + // Add the middleware to the middleware queue + $middlewareQueue->add(new AuthenticationMiddleware($this)); + + return $middlewareQueue; +} +``` + +### Migrate AuthComponent settings + +The configuration array from `AuthComponent` needs to be split into +identifiers and authenticators when configuring the service. So when you +had your `AuthComponent` configured this way: + +``` php +$this->loadComponent('Auth', [ + 'authentication' => [ + 'Form' => [ + 'fields' => [ + 'username' => 'email', + 'password' => 'password', + ] + ] + ] +]); +``` + +You’ll now have to configure it this way: + +``` php +// Instantiate the service +$service = new AuthenticationService(); + +// Define identifier + $passwordIdentifier = [ + 'Authentication.Password' => [ + 'fields' => [ + 'username' => 'email', + 'password' => 'password' + ] + ], + ]; + + // Load the authenticators + $service->loadAuthenticator('Authentication.Session', [ + 'identifier' => $passwordIdentifier, + ]); + $service->loadAuthenticator('Authentication.Form', [ + 'identifier' => $passwordIdentifier, + ]); +``` + +If you have customized the `userModel` you can use the following +configuration: + +``` php +// Instantiate the service +$service = new AuthenticationService(); + +// Define identifier +$passwordIdentifier = [ + 'Authentication.Password' => [ + 'resolver' => [ + 'className' => 'Authentication.Orm', + 'userModel' => 'Employees', + ], + 'fields' => [ + 'username' => 'email', + 'password' => 'password' + ] + ], +]; +``` + +While there is a bit more code than before, you have more flexibility in +how your authentication is handled. + +### Login action + +The `AuthenticationMiddleware` will handle checking and setting the +identity based on your authenticators. Usually after logging in, +`AuthComponent` would redirect to a configured location. To redirect +upon a successful login, change your login action to check the new +identity results: + +``` php +public function login() +{ + $result = $this->Authentication->getResult(); + + // regardless of POST or GET, redirect if user is logged in + if ($result->isValid()) { + $target = $this->Authentication->getLoginRedirect(); + return $this->redirect($target); + } + + // display error if user submitted and authentication failed + if ($this->request->is(['post'])) { + $this->Flash->error('Invalid username or password'); + } +} +``` + +### Checking identities + +After applying the middleware you can use identity data by using the +`identity` request attribute. This replaces the +`$this->Auth->user()` calls you are using now. If the current +user is unauthenticated or if the provided credentials were invalid, the +`identity` attribute will be `null`: + +``` php +$user = $request->getAttribute('identity'); +``` + +For more details about the result of the authentication process you can +access the result object that also comes with the request and is +accessible on the `authentication` attribute: + +``` php +$result = $request->getAttribute('authentication')->getResult(); +// Boolean if the result is valid +$isValid = $result->isValid(); +// A status code +$statusCode = $result->getStatus(); +// An array of error messages or data if the identifier provided any +$errors = $result->getErrors(); +``` + +Any place you were calling `AuthComponent::setUser()`, you should now +use `setIdentity()`: + +``` php +// Assume you need to read a user by access token +$user = $this->Users->find('byToken', ['token' => $token])->first(); + +// Persist the user into configured authenticators. +$this->Authentication->setIdentity($user); +``` + +### Migrating allow/deny logic + +Like `AuthComponent` the `AuthenticationComponent` makes it easy to +make specific actions ‘public’ and not require a valid identity to be +present: + +``` php +// In your controller's beforeFilter method. +$this->Authentication->allowUnauthenticated(['view']); +``` + +Each call to `allowUnauthenticated()` will overwrite the current +action list. + +To mimic `$this->Auth->deny(['register']);` you can do: + +``` php +$action = $this->getRequest()->getParam('action'); +if ($action !== 'register') { + $this->Authentication->allowUnauthenticated([$action]); +} +``` + +## Migrating Unauthenticated Redirects + +By default `AuthComponent` redirects users back to the login page when +authentication is required. In contrast, the `AuthenticationComponent` +in this plugin will raise an exception in this scenario. You can convert +this exception into a redirect using the `unauthenticatedRedirect` +when configuring the `AuthenticationService`. + +You can also pass the current request target URI as a query parameter +using the `queryParam` option. Note that the redirect parameter is only +appended for GET requests to prevent redirecting to non-GET actions after login: + +``` php +// In the getAuthenticationService() method of your src/Application.php + +$service = new AuthenticationService(); + +// Configure unauthenticated redirect +$service->setConfig([ + 'unauthenticatedRedirect' => '/users/login', + 'queryParam' => 'redirect', +]); +``` + +Then in your controller's login method you can use `getLoginRedirect()` to get +the redirect target safely from the query string parameter: + +``` php +public function login() +{ + $result = $this->Authentication->getResult(); + + // Regardless of POST or GET, redirect if user is logged in + if ($result->isValid()) { + // Use the redirect parameter if present. + $target = $this->Authentication->getLoginRedirect(); + if (!$target) { + $target = ['controller' => 'Pages', 'action' => 'display', 'home']; + } + return $this->redirect($target); + } +} +``` + +## Migrating Hashing Upgrade Logic + +If your application uses `AuthComponent`’s hash upgrade +functionality. You can replicate that logic with this plugin by +leveraging the `AuthenticationService`: + +``` php +public function login() +{ + $result = $this->Authentication->getResult(); + + // regardless of POST or GET, redirect if user is logged in + if ($result->isValid()) { + $authService = $this->Authentication->getAuthenticationService(); + + // Get the identifier that was used for authentication. + $identifier = $authService->getIdentificationProvider(); + if ($identifier !== null && $identifier->needsPasswordRehash()) { + // Rehash happens on save. + $user = $this->Users->get($this->Authentication->getIdentityData('id')); + $user->password = $this->request->getData('password'); + $this->Users->save($user); + } + + // Redirect to a logged in page + return $this->redirect([ + 'controller' => 'Pages', + 'action' => 'display', + 'home' + ]); + } +} +``` diff --git a/docs/en/migration-from-the-authcomponent.rst b/docs/en/migration-from-the-authcomponent.rst deleted file mode 100644 index ab7d3b31..00000000 --- a/docs/en/migration-from-the-authcomponent.rst +++ /dev/null @@ -1,328 +0,0 @@ -Migration from the AuthComponent -################################ - -Differences -=========== - -- This plugin intentionally **does not** handle authorization. It was - `decoupled `__ - from authorization on purpose for a clear `separation of - concerns `__. - See also `Computer access - control `__. - This plugin handles only *identification* and *authentication*. We - might have another plugin for authorization. -- There is no automatic checking of the session. To get the actual user - data from the session you’ll have to use the - ``SessionAuthenticator``. It will check the session if there is data - in the configured session key and put it into the identity object. -- The user data is no longer available through the old AuthComponent but is - accessible via a request attribute and encapsulated in an identity - object: ``$request->getAttribute('authentication')->getIdentity();``. - Additionally, you can leverage the ``AuthenticationComponent`` ``getIdentity()`` or ``getIdentityData()`` methods. -- The logic of the authentication process has been split into - authenticators and identifiers. An authenticator will extract the - credentials from the request, while identifiers verify the - credentials and find the matching user. -- DigestAuthenticate has been renamed to HttpDigestAuthenticator -- BasicAuthenticate has been renamed to HttpBasicAuthenticator - -Similarities -============ - -- All the existing authentication adapters, Form, Basic, Digest are - still there but have been refactored into authenticators. - -Identifiers and authenticators -============================== - -Following the principle of separation of concerns, the former -authentication objects were split into separate objects, identifiers and -authenticators. - -- **Authenticators** take the incoming request and try to extract - identification credentials from it. If credentials are found, they - are passed to a collection of identifiers where the user is located. - For that reason authenticators take an IdentifierCollection as first - constructor argument. -- **Identifiers** verify identification credentials against a storage - system. eg. (ORM tables, LDAP etc) and return identified user data. - -This makes it easy to change the identification logic as needed or use -several sources of user data. - -If you want to implement your own identifiers, your identifier must -implement the ``IdentifierInterface``. - -Migrating your authentication setup -=================================== - -The first step to migrating your application is to load the authentication -plugin in your application's bootstrap method:: - - public function bootstrap(): void - { - parent::bootstrap(); - $this->addPlugin('Authentication'); - } - -Then update your application to implement the authentication provider interface. -This lets the AuthenticationMiddleware know how to get the authentication -service from your application:: - - // in src/Application.php - - // Add the following use statements. - use Authentication\AuthenticationService; - use Authentication\AuthenticationServiceInterface; - use Authentication\AuthenticationServiceProviderInterface; - use Authentication\Middleware\AuthenticationMiddleware; - use Psr\Http\Message\ResponseInterface; - use Psr\Http\Message\ServerRequestInterface; - - // Add the authentication interface. - class Application extends BaseApplication implements AuthenticationServiceProviderInterface - { - /** - * Returns a service provider instance. - * - * @param \Psr\Http\Message\ServerRequestInterface $request Request - * @param \Psr\Http\Message\ResponseInterface $response Response - * @return \Authentication\AuthenticationServiceInterface - */ - public function getAuthenticationService(ServerRequestInterface $request) : AuthenticationServiceInterface - { - $service = new AuthenticationService(); - // Configure the service. (see below for more details) - return $service; - } - } - -Next add the ``AuthenticationMiddleware`` to your application:: - - // in src/Application.php - public function middleware($middlewareQueue) - { - // Various other middlewares for error handling, routing etc. added here. - - // Add the middleware to the middleware queue - $middlewareQueue->add(new AuthenticationMiddleware($this)); - - return $middlewareQueue; - } - -Migrate AuthComponent settings ------------------------------- - -The configuration array from ``AuthComponent`` needs to be split into -identifiers and authenticators when configuring the service. So when you -had your ``AuthComponent`` configured this way:: - - $this->loadComponent('Auth', [ - 'authentication' => [ - 'Form' => [ - 'fields' => [ - 'username' => 'email', - 'password' => 'password', - ] - ] - ] - ]); - -You’ll now have to configure it this way:: - - // Instantiate the service - $service = new AuthenticationService(); - - // Define identifier - $passwordIdentifier = [ - 'Authentication.Password' => [ - 'fields' => [ - 'username' => 'email', - 'password' => 'password' - ] - ], - ]; - - // Load the authenticators - $service->loadAuthenticator('Authentication.Session', [ - 'identifier' => $passwordIdentifier, - ]); - $service->loadAuthenticator('Authentication.Form', [ - 'identifier' => $passwordIdentifier, - ]); - -If you have customized the ``userModel`` you can use the following -configuration:: - - // Instantiate the service - $service = new AuthenticationService(); - - // Define identifier - $passwordIdentifier = [ - 'Authentication.Password' => [ - 'resolver' => [ - 'className' => 'Authentication.Orm', - 'userModel' => 'Employees', - ], - 'fields' => [ - 'username' => 'email', - 'password' => 'password' - ] - ], - ]; - -While there is a bit more code than before, you have more flexibility in -how your authentication is handled. - -Login action ------------- - -The ``AuthenticationMiddleware`` will handle checking and setting the -identity based on your authenticators. Usually after logging in, -``AuthComponent`` would redirect to a configured location. To redirect -upon a successful login, change your login action to check the new -identity results:: - - public function login() - { - $result = $this->Authentication->getResult(); - - // regardless of POST or GET, redirect if user is logged in - if ($result->isValid()) { - $target = $this->Authentication->getLoginRedirect(); - return $this->redirect($target); - } - - // display error if user submitted and authentication failed - if ($this->request->is(['post'])) { - $this->Flash->error('Invalid username or password'); - } - } - -Checking identities -------------------- - -After applying the middleware you can use identity data by using the -``identity`` request attribute. This replaces the -``$this->Auth->user()`` calls you are using now. If the current -user is unauthenticated or if the provided credentials were invalid, the -``identity`` attribute will be ``null``:: - - $user = $request->getAttribute('identity'); - -For more details about the result of the authentication process you can -access the result object that also comes with the request and is -accessible on the ``authentication`` attribute:: - - $result = $request->getAttribute('authentication')->getResult(); - // Boolean if the result is valid - $isValid = $result->isValid(); - // A status code - $statusCode = $result->getStatus(); - // An array of error messages or data if the identifier provided any - $errors = $result->getErrors(); - -Any place you were calling ``AuthComponent::setUser()``, you should now -use ``setIdentity()``:: - - // Assume you need to read a user by access token - $user = $this->Users->find('byToken', ['token' => $token])->first(); - - // Persist the user into configured authenticators. - $this->Authentication->setIdentity($user); - - -Migrating allow/deny logic --------------------------- - -Like ``AuthComponent`` the ``AuthenticationComponent`` makes it easy to -make specific actions ‘public’ and not require a valid identity to be -present:: - - // In your controller's beforeFilter method. - $this->Authentication->allowUnauthenticated(['view']); - -Each call to ``allowUnauthenticated()`` will overwrite the current -action list. - -To mimic ``$this->Auth->deny(['register']);`` you can do:: - - $action = $this->getRequest()->getParam('action'); - if ($action !== 'register') { - $this->Authentication->allowUnauthenticated([$action]); - } - -Migrating Unauthenticated Redirects -=================================== - -By default ``AuthComponent`` redirects users back to the login page when -authentication is required. In contrast, the ``AuthenticationComponent`` -in this plugin will raise an exception in this scenario. You can convert -this exception into a redirect using the ``unauthenticatedRedirect`` -when configuring the ``AuthenticationService``. - -You can also pass the current request target URI as a query parameter -using the ``queryParam`` option. Note that the redirect parameter is only -appended for GET requests to prevent redirecting to non-GET actions after login:: - - // In the getAuthenticationService() method of your src/Application.php - - $service = new AuthenticationService(); - - // Configure unauthenticated redirect - $service->setConfig([ - 'unauthenticatedRedirect' => '/users/login', - 'queryParam' => 'redirect', - ]); - -Then in your controller's login method you can use ``getLoginRedirect()`` to get -the redirect target safely from the query string parameter:: - - public function login() - { - $result = $this->Authentication->getResult(); - - // Regardless of POST or GET, redirect if user is logged in - if ($result->isValid()) { - // Use the redirect parameter if present. - $target = $this->Authentication->getLoginRedirect(); - if (!$target) { - $target = ['controller' => 'Pages', 'action' => 'display', 'home']; - } - return $this->redirect($target); - } - } - -Migrating Hashing Upgrade Logic -=============================== - -If your application uses ``AuthComponent``\ ’s hash upgrade -functionality. You can replicate that logic with this plugin by -leveraging the ``AuthenticationService``:: - - public function login() - { - $result = $this->Authentication->getResult(); - - // regardless of POST or GET, redirect if user is logged in - if ($result->isValid()) { - $authService = $this->Authentication->getAuthenticationService(); - - // Get the identifier that was used for authentication. - $identifier = $authService->getIdentificationProvider(); - if ($identifier !== null && $identifier->needsPasswordRehash()) { - // Rehash happens on save. - $user = $this->Users->get($this->Authentication->getIdentityData('id')); - $user->password = $this->request->getData('password'); - $this->Users->save($user); - } - - // Redirect to a logged in page - return $this->redirect([ - 'controller' => 'Pages', - 'action' => 'display', - 'home' - ]); - } - } diff --git a/docs/en/password-hashers.md b/docs/en/password-hashers.md new file mode 100644 index 00000000..bac0a15c --- /dev/null +++ b/docs/en/password-hashers.md @@ -0,0 +1,80 @@ +# Password Hashers + +## Default + +This is using the php constant `PASSWORD_DEFAULT` for the encryption +method. The default hash type is `bcrypt`. + +See [the php +documentation](https://php.net/manual/en/function.password-hash.php) +for further information on bcrypt and PHP’s password hashing. + +The config options for this adapter are: + +- **hashType**: Hashing algorithm to use. Valid values are those + supported by `$algo` argument of `password_hash()`. Defaults to + `PASSWORD_DEFAULT` +- **hashOptions**: Associative array of options. Check the PHP manual + for supported options for each hash type. Defaults to empty array. + +## Legacy + +This is a password hasher for applications that have migrated from +CakePHP2. + +## Fallback + +The fallback password hasher allows you to configure multiple hashers +and will check them sequentially. This allows users to login with an old +hash type until their password is reset and upgraded to a new hash. + +## Upgrading Hashing Algorithms + +CakePHP provides a clean way to migrate your users’ passwords from one +algorithm to another, this is achieved through the +`FallbackPasswordHasher` class. Assuming you want to migrate from a +Legacy password to the Default bcrypt hasher, you can configure the +fallback hasher as follows: + +``` php +$passwordIdentifier = [ + 'Authentication.Password' => [ + // Other config options + 'passwordHasher' => [ + 'className' => 'Authentication.Fallback', + 'hashers' => [ + 'Authentication.Default', + [ + 'className' => 'Authentication.Legacy', + 'hashType' => 'md5', + 'salt' => false, // turn off default usage of salt + ], + ] + ] + ], +]; +``` + +Then in your login action you can use the authentication service to +access the `Password` identifier and check if the current user’s +password needs to be upgraded: + +``` php +public function login() +{ + $authentication = $this->request->getAttribute('authentication'); + $result = $authentication->getResult(); + + // regardless of POST or GET, redirect if user is logged in + if ($result->isValid()) { + if ($authentication->getIdentificationProvider()->needsPasswordRehash()) { + // Rehash happens on save. + $user = $this->Users->get($authentication->getIdentity()->getIdentifier()); + $user->password = $this->request->getData('password'); + $this->Users->saveOrFail($user); + } + + // Redirect or display a template. + } +} +``` diff --git a/docs/en/password-hashers.rst b/docs/en/password-hashers.rst deleted file mode 100644 index 90306560..00000000 --- a/docs/en/password-hashers.rst +++ /dev/null @@ -1,81 +0,0 @@ -Password Hashers -################ - -Default -======= - -This is using the php constant ``PASSWORD_DEFAULT`` for the encryption -method. The default hash type is ``bcrypt``. - -See `the php -documentation `__ -for further information on bcrypt and PHP’s password hashing. - -The config options for this adapter are: - -- **hashType**: Hashing algorithm to use. Valid values are those - supported by ``$algo`` argument of ``password_hash()``. Defaults to - ``PASSWORD_DEFAULT`` -- **hashOptions**: Associative array of options. Check the PHP manual - for supported options for each hash type. Defaults to empty array. - -Legacy -====== - -This is a password hasher for applications that have migrated from -CakePHP2. - -Fallback -======== - -The fallback password hasher allows you to configure multiple hashers -and will check them sequentially. This allows users to login with an old -hash type until their password is reset and upgraded to a new hash. - -Upgrading Hashing Algorithms -============================ - -CakePHP provides a clean way to migrate your users’ passwords from one -algorithm to another, this is achieved through the -``FallbackPasswordHasher`` class. Assuming you want to migrate from a -Legacy password to the Default bcrypt hasher, you can configure the -fallback hasher as follows:: - - $passwordIdentifier = [ - 'Authentication.Password' => [ - // Other config options - 'passwordHasher' => [ - 'className' => 'Authentication.Fallback', - 'hashers' => [ - 'Authentication.Default', - [ - 'className' => 'Authentication.Legacy', - 'hashType' => 'md5', - 'salt' => false, // turn off default usage of salt - ], - ] - ] - ], - ]; - -Then in your login action you can use the authentication service to -access the ``Password`` identifier and check if the current user’s -password needs to be upgraded:: - - public function login() - { - $authentication = $this->request->getAttribute('authentication'); - $result = $authentication->getResult(); - - // regardless of POST or GET, redirect if user is logged in - if ($result->isValid()) { - if ($authentication->getIdentificationProvider()->needsPasswordRehash()) { - // Rehash happens on save. - $user = $this->Users->get($authentication->getIdentity()->getIdentifier()); - $user->password = $this->request->getData('password'); - $this->Users->saveOrFail($user); - } - - // Redirect or display a template. - } - } diff --git a/docs/en/redirect-validation.md b/docs/en/redirect-validation.md new file mode 100644 index 00000000..4ca08b12 --- /dev/null +++ b/docs/en/redirect-validation.md @@ -0,0 +1,176 @@ +# Redirect Validation + +The Authentication plugin provides optional redirect validation to prevent redirect loop attacks +and malicious redirect patterns that could be exploited by bots or attackers. + + + +## Preventing Redirect Loops + +By default, the authentication service does not validate redirect URLs beyond checking that they +are relative (not external). This means that malicious actors or misconfigured bots could create +deeply nested redirect chains like: + +``` text +/login?redirect=/login?redirect=/login?redirect=/protected/page +``` + +These nested redirects can waste server resources, pollute logs, and potentially enable security +exploits. + +## Enabling Redirect Validation + +To enable redirect validation, configure the `redirectValidation` option in your +`AuthenticationService`: + +``` php +// In src/Application.php getAuthenticationService() method +$service = new AuthenticationService(); +$service->setConfig([ + 'unauthenticatedRedirect' => '/users/login', + 'queryParam' => 'redirect', + 'redirectValidation' => [ + 'enabled' => true, // Enable validation (default: false) + ], +]); +``` + +## Configuration Options + +The `redirectValidation` configuration accepts the following options: + +enabled +**Type:** `bool` \| **Default:** `false` + +Whether to enable redirect validation. Disabled by default for backward compatibility. + +maxDepth +**Type:** `int` \| **Default:** `1` + +Maximum number of nested redirect parameters allowed. For example, with `maxDepth` set to 1, +`/login?redirect=/articles` is valid, but `/login?redirect=/login?redirect=/articles` is blocked. + +maxEncodingLevels +**Type:** `int` \| **Default:** `1` + +Maximum URL encoding levels allowed. This prevents obfuscation attacks using double or triple +encoding (e.g., `%252F` for double-encoded `/`). + +maxLength +**Type:** `int` \| **Default:** `2000` + +Maximum allowed length of the redirect URL in characters. This helps prevent DOS attacks +via excessively long URLs. + +## Example Configuration + +Here's a complete example with custom configuration: + +``` php +$service = new AuthenticationService(); +$service->setConfig([ + 'unauthenticatedRedirect' => '/users/login', + 'queryParam' => 'redirect', + 'redirectValidation' => [ + 'enabled' => true, + 'maxDepth' => 1, + 'maxEncodingLevels' => 1, + 'maxLength' => 2000, + ], +]); +``` + +## How Validation Works + +When redirect validation is enabled and a redirect URL fails validation, `getLoginRedirect()` +will return `null` instead of the invalid URL. Your application should handle this by +redirecting to a default location: + +``` php +// In your controller +$target = $this->Authentication->getLoginRedirect() ?? '/'; +return $this->redirect($target); +``` + +## Validation Checks + +The validation performs the following checks in order: + +1. **Redirect Depth**: Counts occurrences of `redirect=` in the decoded URL +2. **Encoding Level**: Counts occurrences of `%25` (percent-encoded percent sign) +3. **URL Length**: Checks total character count + +If any check fails, the URL is rejected. + +## Custom Validation + +You can extend `AuthenticationService` and override the `validateRedirect()` method +to implement custom validation logic, such as blocking specific URL patterns: + +``` php +namespace App\Auth; + +use Authentication\AuthenticationService; + +class CustomAuthenticationService extends AuthenticationService +{ + protected function validateRedirect(string $redirect): ?string + { + // Call parent validation first + $redirect = parent::validateRedirect($redirect); + + if ($redirect === null) { + return null; + } + + // Add your custom validation + // Example: Block redirects to authentication pages + if (preg_match('#/(login|logout|register)#i', $redirect)) { + return null; + } + + // Example: Block redirects to admin areas + if (str_contains($redirect, '/admin')) { + return null; + } + + return $redirect; + } +} +``` + +## Backward Compatibility + +Redirect validation is **disabled by default** to maintain backward compatibility with existing +applications. To enable it, explicitly set `'enabled' => true` in the configuration. + +## Security Considerations + +While redirect validation helps prevent common attacks, it should be part of a comprehensive +security strategy that includes: + +- Rate limiting to prevent bot abuse +- Monitoring and logging of blocked redirects +- Regular security audits +- Keeping the Authentication plugin up to date + +## Real-World Attack Example + +In production environments, bots (especially AI crawlers like GPTBot) have been observed +creating redirect chains with 6-7 levels of nesting: + +``` text +/login?redirect=%2Flogin%3Fredirect%3D%252Flogin%253Fredirect%253D... +``` + +Enabling redirect validation prevents these attacks and protects your application from: + +- Resource exhaustion (CPU wasted parsing deeply nested URLs) +- Log pollution (malformed URLs flooding access logs) +- SEO damage (search engines indexing login pages with loops) +- Potential security exploits when combined with other vulnerabilities + +For more information on redirect attacks, see: + +- [OWASP: Unvalidated Redirects and Forwards](https://owasp.org/www-community/attacks/Unvalidated_Redirects_and_Forwards) +- [CWE-601: URL Redirection to Untrusted Site](https://cwe.mitre.org/data/definitions/601.html) diff --git a/docs/en/redirect-validation.rst b/docs/en/redirect-validation.rst deleted file mode 100644 index a08301c8..00000000 --- a/docs/en/redirect-validation.rst +++ /dev/null @@ -1,187 +0,0 @@ -Redirect Validation -################### - -The Authentication plugin provides optional redirect validation to prevent redirect loop attacks -and malicious redirect patterns that could be exploited by bots or attackers. - -.. _security-redirect-loops: - -Preventing Redirect Loops -========================== - -By default, the authentication service does not validate redirect URLs beyond checking that they -are relative (not external). This means that malicious actors or misconfigured bots could create -deeply nested redirect chains like: - -.. code-block:: text - - /login?redirect=/login?redirect=/login?redirect=/protected/page - -These nested redirects can waste server resources, pollute logs, and potentially enable security -exploits. - -Enabling Redirect Validation -============================= - -To enable redirect validation, configure the ``redirectValidation`` option in your -``AuthenticationService``: - -.. code-block:: php - - // In src/Application.php getAuthenticationService() method - $service = new AuthenticationService(); - $service->setConfig([ - 'unauthenticatedRedirect' => '/users/login', - 'queryParam' => 'redirect', - 'redirectValidation' => [ - 'enabled' => true, // Enable validation (default: false) - ], - ]); - -Configuration Options -===================== - -The ``redirectValidation`` configuration accepts the following options: - -enabled - **Type:** ``bool`` | **Default:** ``false`` - - Whether to enable redirect validation. Disabled by default for backward compatibility. - -maxDepth - **Type:** ``int`` | **Default:** ``1`` - - Maximum number of nested redirect parameters allowed. For example, with ``maxDepth`` set to 1, - ``/login?redirect=/articles`` is valid, but ``/login?redirect=/login?redirect=/articles`` is blocked. - -maxEncodingLevels - **Type:** ``int`` | **Default:** ``1`` - - Maximum URL encoding levels allowed. This prevents obfuscation attacks using double or triple - encoding (e.g., ``%252F`` for double-encoded ``/``). - -maxLength - **Type:** ``int`` | **Default:** ``2000`` - - Maximum allowed length of the redirect URL in characters. This helps prevent DOS attacks - via excessively long URLs. - -Example Configuration -===================== - -Here's a complete example with custom configuration: - -.. code-block:: php - - $service = new AuthenticationService(); - $service->setConfig([ - 'unauthenticatedRedirect' => '/users/login', - 'queryParam' => 'redirect', - 'redirectValidation' => [ - 'enabled' => true, - 'maxDepth' => 1, - 'maxEncodingLevels' => 1, - 'maxLength' => 2000, - ], - ]); - -How Validation Works -==================== - -When redirect validation is enabled and a redirect URL fails validation, ``getLoginRedirect()`` -will return ``null`` instead of the invalid URL. Your application should handle this by -redirecting to a default location: - -.. code-block:: php - - // In your controller - $target = $this->Authentication->getLoginRedirect() ?? '/'; - return $this->redirect($target); - -Validation Checks -================= - -The validation performs the following checks in order: - -1. **Redirect Depth**: Counts occurrences of ``redirect=`` in the decoded URL -2. **Encoding Level**: Counts occurrences of ``%25`` (percent-encoded percent sign) -3. **URL Length**: Checks total character count - -If any check fails, the URL is rejected. - -Custom Validation -================= - -You can extend ``AuthenticationService`` and override the ``validateRedirect()`` method -to implement custom validation logic, such as blocking specific URL patterns: - -.. code-block:: php - - namespace App\Auth; - - use Authentication\AuthenticationService; - - class CustomAuthenticationService extends AuthenticationService - { - protected function validateRedirect(string $redirect): ?string - { - // Call parent validation first - $redirect = parent::validateRedirect($redirect); - - if ($redirect === null) { - return null; - } - - // Add your custom validation - // Example: Block redirects to authentication pages - if (preg_match('#/(login|logout|register)#i', $redirect)) { - return null; - } - - // Example: Block redirects to admin areas - if (str_contains($redirect, '/admin')) { - return null; - } - - return $redirect; - } - } - -Backward Compatibility -====================== - -Redirect validation is **disabled by default** to maintain backward compatibility with existing -applications. To enable it, explicitly set ``'enabled' => true`` in the configuration. - -Security Considerations -======================= - -While redirect validation helps prevent common attacks, it should be part of a comprehensive -security strategy that includes: - -* Rate limiting to prevent bot abuse -* Monitoring and logging of blocked redirects -* Regular security audits -* Keeping the Authentication plugin up to date - -Real-World Attack Example -========================= - -In production environments, bots (especially AI crawlers like GPTBot) have been observed -creating redirect chains with 6-7 levels of nesting: - -.. code-block:: text - - /login?redirect=%2Flogin%3Fredirect%3D%252Flogin%253Fredirect%253D... - -Enabling redirect validation prevents these attacks and protects your application from: - -* Resource exhaustion (CPU wasted parsing deeply nested URLs) -* Log pollution (malformed URLs flooding access logs) -* SEO damage (search engines indexing login pages with loops) -* Potential security exploits when combined with other vulnerabilities - -For more information on redirect attacks, see: - -* `OWASP: Unvalidated Redirects and Forwards `_ -* `CWE-601: URL Redirection to Untrusted Site `_ diff --git a/docs/en/testing.md b/docs/en/testing.md new file mode 100644 index 00000000..f9e626fb --- /dev/null +++ b/docs/en/testing.md @@ -0,0 +1,95 @@ +# Testing with Authentication + +With the `authentication` middleware active in your application you'll +need to simulate authentication credentials in your integration tests. First, +ensure that your controller or middleware tests are using the +`IntegrationTestTrait`: + +``` php +// In a controller test. +use Cake\TestSuite\IntegrationTestTrait; +use Cake\TestSuite\TestCase; + +class ArticlesControllerTest extends TestCase +{ + use IntegrationTestTrait; + + // Test methods and helpers to follow. +} +``` + +Based on the type of authentication you're using you will need to +simulate credentials differently. Lets review a few more common types of +authentication. + +## Session based authentication + +Session based authentication requires simulating the User data that +normally would be found in the session. In your test cases you can +define a helper method that lets you 'login': + +``` php +protected function login($userId = 1) +{ + $users = TableRegistry::getTableLocator()->get('Users'); + $user = $users->get($userId); + $this->session(['Auth' => $user]); +} +``` + +In your integration tests you can use `login()` to simulate a user +being logged in: + +``` php +public function testGet() +{ + $this->login(); + $this->get('/bookmarks/1'); + $this->assertResponseOk(); +} +``` + +## Token based authentication + +With token based authentication you need to simulate the +`Authorization` header. After getting valid token setup the request: + +``` php +protected function getToken(): string +{ + // Get a token for a known user + $user = $this->fetchTable('Users')->get(1, contain: ['ApiTokens']); + + return $user->api_tokens[0]->token; +} + +public function testGet() +{ + $token = $this->getToken(); + $this->configRequest([ + 'headers' => ['Authorization' => 'Bearer ' . $token] + ]); + $this->get('/api/bookmarks'); + $this->assertResponseOk(); +} +``` + +## Basic/Digest based authentication + +When testing Basic or Digest Authentication, you can add the environment +variables that [PHP creates](https://php.net/manual/en/features.http-auth.php) +automatically: + +``` php +public function testGet() +{ + $this->configRequest([ + 'environment' => [ + 'PHP_AUTH_USER' => 'username', + 'PHP_AUTH_PW' => 'password', + ] + ]); + $this->get('/api/bookmarks'); + $this->assertResponseOk(); +} +``` diff --git a/docs/en/testing.rst b/docs/en/testing.rst deleted file mode 100644 index 6138da28..00000000 --- a/docs/en/testing.rst +++ /dev/null @@ -1,90 +0,0 @@ -Testing with Authentication -########################### - -With the ``authentication`` middleware active in your application you'll -need to simulate authentication credentials in your integration tests. First, -ensure that your controller or middleware tests are using the -``IntegrationTestTrait``:: - - // In a controller test. - use Cake\TestSuite\IntegrationTestTrait; - use Cake\TestSuite\TestCase; - - class ArticlesControllerTest extends TestCase - { - use IntegrationTestTrait; - - // Test methods and helpers to follow. - } - -Based on the type of authentication you're using you will need to -simulate credentials differently. Lets review a few more common types of -authentication. - -Session based authentication -============================ - -Session based authentication requires simulating the User data that -normally would be found in the session. In your test cases you can -define a helper method that lets you 'login':: - - protected function login($userId = 1) - { - $users = TableRegistry::getTableLocator()->get('Users'); - $user = $users->get($userId); - $this->session(['Auth' => $user]); - } - -In your integration tests you can use ``login()`` to simulate a user -being logged in:: - - public function testGet() - { - $this->login(); - $this->get('/bookmarks/1'); - $this->assertResponseOk(); - } - -Token based authentication -========================== - -With token based authentication you need to simulate the -``Authorization`` header. After getting valid token setup the request:: - - protected function getToken(): string - { - // Get a token for a known user - $user = $this->fetchTable('Users')->get(1, contain: ['ApiTokens']); - - return $user->api_tokens[0]->token; - } - - public function testGet() - { - $token = $this->getToken(); - $this->configRequest([ - 'headers' => ['Authorization' => 'Bearer ' . $token] - ]); - $this->get('/api/bookmarks'); - $this->assertResponseOk(); - } - - -Basic/Digest based authentication -================================= - -When testing Basic or Digest Authentication, you can add the environment -variables that `PHP creates `_ -automatically.:: - - public function testGet() - { - $this->configRequest([ - 'environment' => [ - 'PHP_AUTH_USER' => 'username', - 'PHP_AUTH_PW' => 'password', - ] - ]); - $this->get('/api/bookmarks'); - $this->assertResponseOk(); - } diff --git a/docs/en/upgrade-2-to-3.md b/docs/en/upgrade-2-to-3.md new file mode 100644 index 00000000..29fee23b --- /dev/null +++ b/docs/en/upgrade-2-to-3.md @@ -0,0 +1,19 @@ +# Upgrading from 2.x to 3.x + +``` bash +composer require cakephp/authentication:^3.0 -W +``` + +or adjust your `composer.json` file manually and perform `composer update -W` + +## Breaking changes + +- Type declarations were added to all function parameter and returns where possible. These are intended + to match the docblock annotations, but include fixes for incorrect annotations. +- Type declarations were added to all class properties where possible. These also include some fixes for + incorrect annotations. +- `\Authentication\Identifier\IdentifierInterface::CREDENTIAL_USERNAME` was moved to `\Authentication\Identifier\AbstractIdentifier::CREDENTIAL_USERNAME`. +- `\Authentication\Identifier\IdentifierInterface::CREDENTIAL_PASSWORD` was moved to `\Authentication\Identifier\AbstractIdentifier::CREDENTIAL_PASSWORD`. +- `\Authentication\Identifier\IdentifierInterface::CREDENTIAL_TOKEN` was moved to `\Authentication\Identifier\TokenIdentifier::CREDENTIAL_TOKEN`. +- `\Authentication\Identifier\IdentifierInterface::CREDENTIAL_JWT_SUBJECT` was moved to `\Authentication\Identifier\JwtSubjectIdentifier::CREDENTIAL_JWT_SUBJECT`. +- `AuthenticationMiddleware` cannot be configured anymore. Configuration needs to happen on the `AuthenticationService` object. diff --git a/docs/en/upgrade-2-to-3.rst b/docs/en/upgrade-2-to-3.rst deleted file mode 100644 index bdaa1e7a..00000000 --- a/docs/en/upgrade-2-to-3.rst +++ /dev/null @@ -1,21 +0,0 @@ -Upgrading from 2.x to 3.x -######################### - -.. code-block:: console - - composer require cakephp/authentication:^3.0 -W - -or adjust your ``composer.json`` file manually and perform ``composer update -W`` - -Breaking changes -================ - -- Type declarations were added to all function parameter and returns where possible. These are intended - to match the docblock annotations, but include fixes for incorrect annotations. -- Type declarations were added to all class properties where possible. These also include some fixes for - incorrect annotations. -- ``\Authentication\Identifier\IdentifierInterface::CREDENTIAL_USERNAME`` was moved to ``\Authentication\Identifier\AbstractIdentifier::CREDENTIAL_USERNAME``. -- ``\Authentication\Identifier\IdentifierInterface::CREDENTIAL_PASSWORD`` was moved to ``\Authentication\Identifier\AbstractIdentifier::CREDENTIAL_PASSWORD``. -- ``\Authentication\Identifier\IdentifierInterface::CREDENTIAL_TOKEN`` was moved to ``\Authentication\Identifier\TokenIdentifier::CREDENTIAL_TOKEN``. -- ``\Authentication\Identifier\IdentifierInterface::CREDENTIAL_JWT_SUBJECT`` was moved to ``\Authentication\Identifier\JwtSubjectIdentifier::CREDENTIAL_JWT_SUBJECT``. -- ``AuthenticationMiddleware`` cannot be configured anymore. Configuration needs to happen on the ``AuthenticationService`` object. diff --git a/docs/en/upgrade-3-to-4.md b/docs/en/upgrade-3-to-4.md new file mode 100644 index 00000000..07751d5f --- /dev/null +++ b/docs/en/upgrade-3-to-4.md @@ -0,0 +1,326 @@ +# Upgrade Guide 3.x to 4.x + +Version 4.0 is a major release with several breaking changes focused on +simplifying the API and removing deprecated code. + +## Breaking Changes + +### IdentifierCollection Removed + +The deprecated `IdentifierCollection` has been removed. Authenticators now +accept a nullable `IdentifierInterface` directly. + +**Before (3.x):** + +``` php +use Authentication\Identifier\IdentifierCollection; + +$identifiers = new IdentifierCollection([ + 'Authentication.Password', +]); + +$authenticator = new FormAuthenticator($identifiers); +``` + +**After (4.x):** + +``` php +use Authentication\Identifier\IdentifierFactory; + +// Option 1: Pass identifier directly +$identifier = IdentifierFactory::create('Authentication.Password'); +$authenticator = new FormAuthenticator($identifier); + +// Option 2: Pass null and let authenticator create default +$authenticator = new FormAuthenticator(null); + +// Option 3: Configure identifier in authenticator config +$service->loadAuthenticator('Authentication.Form', [ + 'identifier' => 'Authentication.Password', +]); +``` + +#### AuthenticationService Changes + +The `loadIdentifier()` method has been removed from `AuthenticationService`. +Identifiers are now managed by individual authenticators. + +**Before (3.x):** + +``` php +$service = new AuthenticationService(); +$service->loadIdentifier('Authentication.Password'); +$service->loadAuthenticator('Authentication.Form'); +``` + +**After (4.x):** + +``` php +$service = new AuthenticationService(); +$service->loadAuthenticator('Authentication.Form', [ + 'identifier' => 'Authentication.Password', +]); +``` + +### CREDENTIAL Constants Moved + +The `CREDENTIAL_USERNAME` and `CREDENTIAL_PASSWORD` constants have been +moved from `AbstractIdentifier` to specific identifier implementations. + +**Before (3.x):** + +``` php +use Authentication\Identifier\AbstractIdentifier; + +$fields = [ + AbstractIdentifier::CREDENTIAL_USERNAME => 'email', + AbstractIdentifier::CREDENTIAL_PASSWORD => 'password', +]; +``` + +**After (4.x):** + +``` php +use Authentication\Identifier\PasswordIdentifier; + +$fields = [ + PasswordIdentifier::CREDENTIAL_USERNAME => 'email', + PasswordIdentifier::CREDENTIAL_PASSWORD => 'password', +]; +``` + +For LDAP authentication: + +``` php +use Authentication\Identifier\LdapIdentifier; + +$fields = [ + LdapIdentifier::CREDENTIAL_USERNAME => 'uid', + LdapIdentifier::CREDENTIAL_PASSWORD => 'password', +]; +``` + +### SessionAuthenticator `identify` Option Removed + +The deprecated `identify` option has been removed from `SessionAuthenticator`. +Use `PrimaryKeySessionAuthenticator` instead if you need to fetch fresh user +data from the database on each request. + +**Before (3.x):** + +``` php +$service->loadAuthenticator('Authentication.Session', [ + 'identify' => true, + 'identifier' => 'Authentication.Password', +]); +``` + +**After (4.x):** + +``` php +$service->loadAuthenticator('Authentication.PrimaryKeySession'); +``` + +### URL Checker Renamed and Restructured + +URL checkers have been completely restructured: + +- `CakeRouterUrlChecker` has been renamed to `DefaultUrlChecker` +- The old `DefaultUrlChecker` has been renamed to `StringUrlChecker` +- Auto-detection has been removed - `DefaultUrlChecker` is now hardcoded + +**Before (3.x):** + +``` php +// Using CakeRouterUrlChecker explicitly +$service->loadAuthenticator('Authentication.Form', [ + 'urlChecker' => 'Authentication.CakeRouter', + 'loginUrl' => [ + 'controller' => 'Users', + 'action' => 'login', + ], +]); + +// Using DefaultUrlChecker explicitly +$service->loadAuthenticator('Authentication.Form', [ + 'urlChecker' => 'Authentication.Default', + 'loginUrl' => '/users/login', +]); + +// Auto-detection (picks CakeRouter if available, otherwise Default) +$service->loadAuthenticator('Authentication.Form', [ + 'loginUrl' => '/users/login', +]); +``` + +**After (4.x):** + +``` php +// DefaultUrlChecker is now hardcoded (formerly CakeRouterUrlChecker) +$service->loadAuthenticator('Authentication.Form', [ + 'loginUrl' => [ + 'controller' => 'Users', + 'action' => 'login', + ], +]); + +// For string-only URL checking, explicitly use StringUrlChecker +$service->loadAuthenticator('Authentication.Form', [ + 'urlChecker' => 'Authentication.String', + 'loginUrl' => '/users/login', +]); +``` + +### Simplified URL Checker API + +URL checkers now accept a single URL in either string or array format. +For multiple URLs, you must explicitly use `MultiUrlChecker`. + +**Multiple URLs - Before (3.x):** + +``` php +// This would auto-select the appropriate checker +$service->loadAuthenticator('Authentication.Form', [ + 'loginUrl' => [ + '/en/users/login', + '/de/users/login', + ], +]); +``` + +**Multiple URLs - After (4.x):** + +``` php +// Must explicitly configure MultiUrlChecker +$service->loadAuthenticator('Authentication.Form', [ + 'urlChecker' => 'Authentication.Multi', + 'loginUrl' => [ + '/en/users/login', + '/de/users/login', + ], +]); +``` + +Single URLs work the same in both versions: + +``` php +// String URL +$service->loadAuthenticator('Authentication.Form', [ + 'loginUrl' => '/users/login', +]); + +// Array URL (CakePHP route) +$service->loadAuthenticator('Authentication.Form', [ + 'loginUrl' => ['controller' => 'Users', 'action' => 'login'], +]); +``` + +### Auto-Detection Removed + +#### URL Checkers + +**Important:** Auto-detection has been removed. `DefaultUrlChecker` is now hardcoded. + +- **4.x default:** Always uses `DefaultUrlChecker` (formerly `CakeUrlChecker`) +- **String URLs only:** Must explicitly configure `StringUrlChecker` +- **Multiple URLs:** Must explicitly configure `MultiUrlChecker` + +#### DefaultUrlChecker is Now CakePHP-Based + +`DefaultUrlChecker` is now the CakePHP checker (formerly `CakeRouterUrlChecker`). +It requires CakePHP Router and supports both string and array URLs. + +The 3.x `DefaultUrlChecker` has been renamed to `StringUrlChecker`. + +``` php +// DefaultUrlChecker now requires CakePHP Router +$checker = new DefaultUrlChecker(); +$checker->check($request, ['controller' => 'Users', 'action' => 'login']); // Works +$checker->check($request, '/users/login'); // Also works + +// For string URL only usage: +$checker = new StringUrlChecker(); +$checker->check($request, '/users/login'); // Works +$checker->check($request, ['controller' => 'Users']); // Throws exception +``` + +## New Features + +### IdentifierFactory + +New factory class for creating identifiers from configuration: + +``` php +use Authentication\Identifier\IdentifierFactory; + +// Create from string +$identifier = IdentifierFactory::create('Authentication.Password'); + +// Create with config +$identifier = IdentifierFactory::create('Authentication.Password', [ + 'fields' => [ + 'username' => 'email', + 'password' => 'password', + ], +]); + +// Pass existing instance (returns as-is) +$identifier = IdentifierFactory::create($existingIdentifier); +``` + +### MultiUrlChecker + +New dedicated checker for multiple login URLs: + +``` php +$service->loadAuthenticator('Authentication.Form', [ + 'urlChecker' => 'Authentication.Multi', + 'loginUrl' => [ + '/en/login', + '/de/login', + ['lang' => 'fr', 'controller' => 'Users', 'action' => 'login'], + ], +]); +``` + +## Migration Tips + +1. **Session Identify**: + + If you used `'identify' => true` on `SessionAuthenticator`, switch to + `PrimaryKeySessionAuthenticator` which always fetches fresh data. + +2. **Search and Replace**: + + - `AbstractIdentifier::CREDENTIAL_` → `PasswordIdentifier::CREDENTIAL_` + - `IdentifierCollection` → `IdentifierFactory` + - `'Authentication.CakeRouter'` → Remove (no longer needed, default is now CakePHP-based) + - `CakeRouterUrlChecker` → `DefaultUrlChecker` + - Old 3.x `DefaultUrlChecker` → `StringUrlChecker` + +3. **String URL Checking**: + + If you want to use string-only URL checking, explicitly configure + `StringUrlChecker`: + + ``` php + $service->loadAuthenticator('Authentication.Form', [ + 'urlChecker' => 'Authentication.String', + 'loginUrl' => '/users/login', + ]); + ``` + +4. **Multiple Login URLs**: + + If you have multiple login URLs, add `'urlChecker' => 'Authentication.Multi'` + to your authenticator configuration. + +5. **Custom Identifier Setup**: + + If you were passing `IdentifierCollection` to authenticators, switch to + either passing a single identifier or null (to use defaults). + +6. **Test Thoroughly**: + + The changes to identifier management and URL checking are significant. + Test all authentication flows after upgrading. diff --git a/docs/en/upgrade-3-to-4.rst b/docs/en/upgrade-3-to-4.rst deleted file mode 100644 index d254f724..00000000 --- a/docs/en/upgrade-3-to-4.rst +++ /dev/null @@ -1,341 +0,0 @@ -Upgrade Guide 3.x to 4.x -######################### - -Version 4.0 is a major release with several breaking changes focused on -simplifying the API and removing deprecated code. - -Breaking Changes -================ - -IdentifierCollection Removed ------------------------------ - -The deprecated ``IdentifierCollection`` has been removed. Authenticators now -accept a nullable ``IdentifierInterface`` directly. - -**Before (3.x):** - -.. code-block:: php - - use Authentication\Identifier\IdentifierCollection; - - $identifiers = new IdentifierCollection([ - 'Authentication.Password', - ]); - - $authenticator = new FormAuthenticator($identifiers); - -**After (4.x):** - -.. code-block:: php - - use Authentication\Identifier\IdentifierFactory; - - // Option 1: Pass identifier directly - $identifier = IdentifierFactory::create('Authentication.Password'); - $authenticator = new FormAuthenticator($identifier); - - // Option 2: Pass null and let authenticator create default - $authenticator = new FormAuthenticator(null); - - // Option 3: Configure identifier in authenticator config - $service->loadAuthenticator('Authentication.Form', [ - 'identifier' => 'Authentication.Password', - ]); - -AuthenticationService Changes -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -The ``loadIdentifier()`` method has been removed from ``AuthenticationService``. -Identifiers are now managed by individual authenticators. - -**Before (3.x):** - -.. code-block:: php - - $service = new AuthenticationService(); - $service->loadIdentifier('Authentication.Password'); - $service->loadAuthenticator('Authentication.Form'); - -**After (4.x):** - -.. code-block:: php - - $service = new AuthenticationService(); - $service->loadAuthenticator('Authentication.Form', [ - 'identifier' => 'Authentication.Password', - ]); - -CREDENTIAL Constants Moved ---------------------------- - -The ``CREDENTIAL_USERNAME`` and ``CREDENTIAL_PASSWORD`` constants have been -moved from ``AbstractIdentifier`` to specific identifier implementations. - -**Before (3.x):** - -.. code-block:: php - - use Authentication\Identifier\AbstractIdentifier; - - $fields = [ - AbstractIdentifier::CREDENTIAL_USERNAME => 'email', - AbstractIdentifier::CREDENTIAL_PASSWORD => 'password', - ]; - -**After (4.x):** - -.. code-block:: php - - use Authentication\Identifier\PasswordIdentifier; - - $fields = [ - PasswordIdentifier::CREDENTIAL_USERNAME => 'email', - PasswordIdentifier::CREDENTIAL_PASSWORD => 'password', - ]; - -For LDAP authentication: - -.. code-block:: php - - use Authentication\Identifier\LdapIdentifier; - - $fields = [ - LdapIdentifier::CREDENTIAL_USERNAME => 'uid', - LdapIdentifier::CREDENTIAL_PASSWORD => 'password', - ]; - -SessionAuthenticator ``identify`` Option Removed -------------------------------------------------- - -The deprecated ``identify`` option has been removed from ``SessionAuthenticator``. -Use ``PrimaryKeySessionAuthenticator`` instead if you need to fetch fresh user -data from the database on each request. - -**Before (3.x):** - -.. code-block:: php - - $service->loadAuthenticator('Authentication.Session', [ - 'identify' => true, - 'identifier' => 'Authentication.Password', - ]); - -**After (4.x):** - -.. code-block:: php - - $service->loadAuthenticator('Authentication.PrimaryKeySession'); - -URL Checker Renamed and Restructured -------------------------------------- - -URL checkers have been completely restructured: - -- ``CakeRouterUrlChecker`` has been renamed to ``DefaultUrlChecker`` -- The old ``DefaultUrlChecker`` has been renamed to ``StringUrlChecker`` -- Auto-detection has been removed - ``DefaultUrlChecker`` is now hardcoded - -**Before (3.x):** - -.. code-block:: php - - // Using CakeRouterUrlChecker explicitly - $service->loadAuthenticator('Authentication.Form', [ - 'urlChecker' => 'Authentication.CakeRouter', - 'loginUrl' => [ - 'controller' => 'Users', - 'action' => 'login', - ], - ]); - - // Using DefaultUrlChecker explicitly - $service->loadAuthenticator('Authentication.Form', [ - 'urlChecker' => 'Authentication.Default', - 'loginUrl' => '/users/login', - ]); - - // Auto-detection (picks CakeRouter if available, otherwise Default) - $service->loadAuthenticator('Authentication.Form', [ - 'loginUrl' => '/users/login', - ]); - -**After (4.x):** - -.. code-block:: php - - // DefaultUrlChecker is now hardcoded (formerly CakeRouterUrlChecker) - $service->loadAuthenticator('Authentication.Form', [ - 'loginUrl' => [ - 'controller' => 'Users', - 'action' => 'login', - ], - ]); - - // For string-only URL checking, explicitly use StringUrlChecker - $service->loadAuthenticator('Authentication.Form', [ - 'urlChecker' => 'Authentication.String', - 'loginUrl' => '/users/login', - ]); - -Simplified URL Checker API ---------------------------- - -URL checkers now accept a single URL in either string or array format. -For multiple URLs, you must explicitly use ``MultiUrlChecker``. - -**Multiple URLs - Before (3.x):** - -.. code-block:: php - - // This would auto-select the appropriate checker - $service->loadAuthenticator('Authentication.Form', [ - 'loginUrl' => [ - '/en/users/login', - '/de/users/login', - ], - ]); - -**Multiple URLs - After (4.x):** - -.. code-block:: php - - // Must explicitly configure MultiUrlChecker - $service->loadAuthenticator('Authentication.Form', [ - 'urlChecker' => 'Authentication.Multi', - 'loginUrl' => [ - '/en/users/login', - '/de/users/login', - ], - ]); - -Single URLs work the same in both versions: - -.. code-block:: php - - // String URL - $service->loadAuthenticator('Authentication.Form', [ - 'loginUrl' => '/users/login', - ]); - - // Array URL (CakePHP route) - $service->loadAuthenticator('Authentication.Form', [ - 'loginUrl' => ['controller' => 'Users', 'action' => 'login'], - ]); - -Auto-Detection Removed ----------------------- - -URL Checkers -^^^^^^^^^^^^ - -**Important:** Auto-detection has been removed. ``DefaultUrlChecker`` is now hardcoded. - -- **4.x default:** Always uses ``DefaultUrlChecker`` (formerly ``CakeUrlChecker``) -- **String URLs only:** Must explicitly configure ``StringUrlChecker`` -- **Multiple URLs:** Must explicitly configure ``MultiUrlChecker`` - -DefaultUrlChecker is Now CakePHP-Based -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -``DefaultUrlChecker`` is now the CakePHP checker (formerly ``CakeRouterUrlChecker``). -It requires CakePHP Router and supports both string and array URLs. - -The 3.x ``DefaultUrlChecker`` has been renamed to ``StringUrlChecker``. - -.. code-block:: php - - // DefaultUrlChecker now requires CakePHP Router - $checker = new DefaultUrlChecker(); - $checker->check($request, ['controller' => 'Users', 'action' => 'login']); // Works - $checker->check($request, '/users/login'); // Also works - - // For string URL only usage: - $checker = new StringUrlChecker(); - $checker->check($request, '/users/login'); // Works - $checker->check($request, ['controller' => 'Users']); // Throws exception - -New Features -============ - -IdentifierFactory ------------------ - -New factory class for creating identifiers from configuration: - -.. code-block:: php - - use Authentication\Identifier\IdentifierFactory; - - // Create from string - $identifier = IdentifierFactory::create('Authentication.Password'); - - // Create with config - $identifier = IdentifierFactory::create('Authentication.Password', [ - 'fields' => [ - 'username' => 'email', - 'password' => 'password', - ], - ]); - - // Pass existing instance (returns as-is) - $identifier = IdentifierFactory::create($existingIdentifier); - -MultiUrlChecker ---------------- - -New dedicated checker for multiple login URLs: - -.. code-block:: php - - $service->loadAuthenticator('Authentication.Form', [ - 'urlChecker' => 'Authentication.Multi', - 'loginUrl' => [ - '/en/login', - '/de/login', - ['lang' => 'fr', 'controller' => 'Users', 'action' => 'login'], - ], - ]); - -Migration Tips -============== - -1. **Session Identify**: - - If you used ``'identify' => true`` on ``SessionAuthenticator``, switch to - ``PrimaryKeySessionAuthenticator`` which always fetches fresh data. - -2. **Search and Replace**: - - - ``AbstractIdentifier::CREDENTIAL_`` → ``PasswordIdentifier::CREDENTIAL_`` - - ``IdentifierCollection`` → ``IdentifierFactory`` - - ``'Authentication.CakeRouter'`` → Remove (no longer needed, default is now CakePHP-based) - - ``CakeRouterUrlChecker`` → ``DefaultUrlChecker`` - - Old 3.x ``DefaultUrlChecker`` → ``StringUrlChecker`` - -3. **String URL Checking**: - - If you want to use string-only URL checking, explicitly configure - ``StringUrlChecker``: - - .. code-block:: php - - $service->loadAuthenticator('Authentication.Form', [ - 'urlChecker' => 'Authentication.String', - 'loginUrl' => '/users/login', - ]); - -4. **Multiple Login URLs**: - - If you have multiple login URLs, add ``'urlChecker' => 'Authentication.Multi'`` - to your authenticator configuration. - -5. **Custom Identifier Setup**: - - If you were passing ``IdentifierCollection`` to authenticators, switch to - either passing a single identifier or null (to use defaults). - -6. **Test Thoroughly**: - - The changes to identifier management and URL checking are significant. - Test all authentication flows after upgrading. diff --git a/docs/en/url-checkers.md b/docs/en/url-checkers.md new file mode 100644 index 00000000..b7d845d3 --- /dev/null +++ b/docs/en/url-checkers.md @@ -0,0 +1,109 @@ +# URL Checkers + +There are URL checkers implemented that allow you to customize the comparison +of the current URL if needed. + +All checkers support single URLs in either string or array format (like `Router::url()`). +For multiple login URLs, use `MultiUrlChecker`. + +## Included Checkers + +### DefaultUrlChecker + +The default URL checker. Supports both string URLs and CakePHP's array-based +routing notation. Uses CakePHP Router and works with named routes. + +Single URL (string): + +``` php +$service->loadAuthenticator('Authentication.Form', [ + 'loginUrl' => '/users/login', +]); +``` + +Single URL (CakePHP route array): + +``` php +$service->loadAuthenticator('Authentication.Form', [ + 'loginUrl' => [ + 'prefix' => false, + 'plugin' => false, + 'controller' => 'Users', + 'action' => 'login', + ], +]); +``` + +Options: + +- **checkFullUrl**: To compare the full URL, including protocol, host + and port or not. Default is `false`. + +### StringUrlChecker + +Checker for string URLs. Supports regex matching. + +``` php +$service->loadAuthenticator('Authentication.Form', [ + 'urlChecker' => 'Authentication.String', + 'loginUrl' => '/users/login', +]); +``` + +Using regex: + +``` php +$service->loadAuthenticator('Authentication.Form', [ + 'urlChecker' => [ + 'className' => 'Authentication.String', + 'useRegex' => true, + ], + 'loginUrl' => '%^/[a-z]{2}/users/login/?$%', +]); +``` + +Options: + +- **checkFullUrl**: To compare the full URL, including protocol, host + and port or not. Default is `false` +- **useRegex**: Compares the URL by a regular expression provided in + the `loginUrl` configuration. + +### MultiUrlChecker + +Use this checker when you need to support multiple login URLs (e.g., for multi-language sites). +You must explicitly configure this checker - it is not auto-detected. + +Multiple string URLs: + +``` php +$service->loadAuthenticator('Authentication.Form', [ + 'urlChecker' => 'Authentication.Multi', + 'loginUrl' => [ + '/en/users/login', + '/de/users/login', + ], +]); +``` + +Multiple CakePHP route arrays: + +``` php +$service->loadAuthenticator('Authentication.Form', [ + 'urlChecker' => 'Authentication.Multi', + 'loginUrl' => [ + ['lang' => 'en', 'controller' => 'Users', 'action' => 'login'], + ['lang' => 'de', 'controller' => 'Users', 'action' => 'login'], + ], +]); +``` + +Options: + +- **checkFullUrl**: To compare the full URL, including protocol, host + and port or not. Default is `false` +- **useRegex**: Compares URLs by regular expressions. Default is `false` + +### Implementing your own Checker + +An URL checkers **must** implement the `UrlCheckerInterface`. diff --git a/docs/en/url-checkers.rst b/docs/en/url-checkers.rst deleted file mode 100644 index fc6a33e2..00000000 --- a/docs/en/url-checkers.rst +++ /dev/null @@ -1,115 +0,0 @@ -URL Checkers -############ - -There are URL checkers implemented that allow you to customize the comparison -of the current URL if needed. - -All checkers support single URLs in either string or array format (like ``Router::url()``). -For multiple login URLs, use ``MultiUrlChecker``. - -Included Checkers -================= - -DefaultUrlChecker ------------------ - -The default URL checker. Supports both string URLs and CakePHP's array-based -routing notation. Uses CakePHP Router and works with named routes. - -Single URL (string): - -.. code-block:: php - - $service->loadAuthenticator('Authentication.Form', [ - 'loginUrl' => '/users/login', - ]); - -Single URL (CakePHP route array): - -.. code-block:: php - - $service->loadAuthenticator('Authentication.Form', [ - 'loginUrl' => [ - 'prefix' => false, - 'plugin' => false, - 'controller' => 'Users', - 'action' => 'login', - ], - ]); - -Options: - -- **checkFullUrl**: To compare the full URL, including protocol, host - and port or not. Default is ``false``. - -StringUrlChecker ------------------ - -Checker for string URLs. Supports regex matching. - -.. code-block:: php - - $service->loadAuthenticator('Authentication.Form', [ - 'urlChecker' => 'Authentication.String', - 'loginUrl' => '/users/login', - ]); - -Using regex: - -.. code-block:: php - - $service->loadAuthenticator('Authentication.Form', [ - 'urlChecker' => [ - 'className' => 'Authentication.String', - 'useRegex' => true, - ], - 'loginUrl' => '%^/[a-z]{2}/users/login/?$%', - ]); - -Options: - -- **checkFullUrl**: To compare the full URL, including protocol, host - and port or not. Default is ``false`` -- **useRegex**: Compares the URL by a regular expression provided in - the ``loginUrl`` configuration. - -MultiUrlChecker ---------------- - -Use this checker when you need to support multiple login URLs (e.g., for multi-language sites). -You must explicitly configure this checker - it is not auto-detected. - -Multiple string URLs: - -.. code-block:: php - - $service->loadAuthenticator('Authentication.Form', [ - 'urlChecker' => 'Authentication.Multi', - 'loginUrl' => [ - '/en/users/login', - '/de/users/login', - ], - ]); - -Multiple CakePHP route arrays: - -.. code-block:: php - - $service->loadAuthenticator('Authentication.Form', [ - 'urlChecker' => 'Authentication.Multi', - 'loginUrl' => [ - ['lang' => 'en', 'controller' => 'Users', 'action' => 'login'], - ['lang' => 'de', 'controller' => 'Users', 'action' => 'login'], - ], - ]); - -Options: - -- **checkFullUrl**: To compare the full URL, including protocol, host - and port or not. Default is ``false`` -- **useRegex**: Compares URLs by regular expressions. Default is ``false`` - -Implementing your own Checker ------------------------------ - -An URL checkers **must** implement the ``UrlCheckerInterface``. diff --git a/docs/en/view-helper.md b/docs/en/view-helper.md new file mode 100644 index 00000000..452884a7 --- /dev/null +++ b/docs/en/view-helper.md @@ -0,0 +1,32 @@ +# View Helper + +In your AppView, load the Helper as: + +``` php +$this->loadHelper('Authentication.Identity'); +``` + +For very simple checking whether the user is logged in you can use: + +``` php +if ($this->Identity->isLoggedIn()) { + ... +} +``` + +Getting user data can be done with: + +``` php +$username = $this->Identity->get('username'); +``` + +The following check can be used to tell if a record that belongs to some +user is the current logged in user and compare other fields as well: + +``` php +$isCurrentUser = $this->Identity->is($user->id); +$isCurrentRole = $this->Identity->is($user->role_id, 'role_id'); +``` + +This method is mostly a convenience method for simple cases and not +intended to replace any kind of proper authorization implementation. diff --git a/docs/en/view-helper.rst b/docs/en/view-helper.rst deleted file mode 100644 index 50d50431..00000000 --- a/docs/en/view-helper.rst +++ /dev/null @@ -1,25 +0,0 @@ -View Helper -=========== - -In your AppView, load the Helper as:: - - $this->loadHelper('Authentication.Identity'); - -For very simple checking whether the user is logged in you can use:: - - if ($this->Identity->isLoggedIn()) { - ... - } - -Getting user data can be done with:: - - $username = $this->Identity->get('username'); - -The following check can be used to tell if a record that belongs to some -user is the current logged in user and compare other fields as well:: - - $isCurrentUser = $this->Identity->is($user->id); - $isCurrentRole = $this->Identity->is($user->role_id, 'role_id'); - -This method is mostly a convenience method for simple cases and not -intended to replace any kind of proper authorization implementation. diff --git a/docs/es/authentication-component.rst b/docs/es/authentication-component.rst deleted file mode 100644 index 72d83213..00000000 --- a/docs/es/authentication-component.rst +++ /dev/null @@ -1,106 +0,0 @@ -Componente Authentication -========================= - -Puede utilizar el ``AuthenticationComponent`` para acceder al resultado de la -autenticación, obtener la identidad del usuario y cerrar la sesión del usuario. Cargue el componente en su -``AppController::initialize()`` como cualquier otro componente:: - - $this->loadComponent('Authentication.Authentication', [ - 'logoutRedirect' => '/users/login' // Default is false - ]); - -Una vez cargado, el ``AuthenticationComponent`` requerirá que todas las acciones tengan un usuario -autenticado presente, pero no realiza otras comprobaciones de control de acceso. Puede deshabilitar -esta verificación para acciones específicas usando ``allowUnauthenticated()``:: - - // In your controller's beforeFilter method. - $this->Authentication->allowUnauthenticated(['view']); - -Accediendo al usuario autenticado ---------------------------------- - -Puede obtener los datos de identidad del usuario autenticado utilizando el componente -authentication:: - - $user = $this->Authentication->getIdentity(); - -También puede obtener la identidad directamente desde la instancia del request:: - - $user = $request->getAttribute('identity'); - -Comprobación del estado de inicio de sesión -------------------------------------------- - -Puede verificar si el proceso de autenticación fue exitoso accediendo al objeto -result:: - - // Using Authentication component - $result = $this->Authentication->getResult(); - - // Using request object - $result = $request->getAttribute('authentication')->getResult(); - - if ($result->isValid()) { - $user = $request->getAttribute('identity'); - } else { - $this->log($result->getStatus()); - $this->log($result->getErrors()); - } - -El estado devuelto por ``getStatus()`` coincidirá con una de estas -constantes en el objeto Result: - -* ``ResultInterface::SUCCESS``, when successful. -* ``ResultInterface::FAILURE_IDENTITY_NOT_FOUND``, when identity could not be found. -* ``ResultInterface::FAILURE_CREDENTIALS_INVALID``, when credentials are invalid. -* ``ResultInterface::FAILURE_CREDENTIALS_MISSING``, when credentials are missing in the request. -* ``ResultInterface::FAILURE_OTHER``, on any other kind of failure. - -El array error devuelto por ``getErrors()`` contiene información **adicional** -procedente del sistema específico contra el que se realizó el intento de autenticación. -Por ejemplo, LDAP u OAuth colocarán aquí los errores específicos de su implementación -para facilitar el registro y la depuración. Pero la mayoría de los autenticadores -incluidos no ponen nada aquí. - -Cerrar sesión en la identidad ------------------------------ - -La sesión de una identidad se cierra simplemente con:: - - $this->Authentication->logout(); - -Si ha establecido la configuración ``logoutRedirect``, ``Authentication::logout()`` devolverá -ese valor, de lo contrario devolverá ``false``. En este caso no realizará ninguna redirección. - -Alternativamente, en lugar del componente, también puede usar el servicio para cerrar sesión:: - - $return = $request->getAttribute('authentication')->clearIdentity($request, $response); - -El resultado devuelto contendrá un array como este:: - - [ - 'response' => object(Cake\Http\Response) { ... }, - 'request' => object(Cake\Http\ServerRequest) { ... }, - ] - -.. note:: - Esto devolverá un array que contiene los objetos request y response, - como ambos son inmutables se obtendran nuevos objetos. Dependiendo del contexto - en el que esté trabajando, tendrá que usar estas instancias a partir de ahora si desea - continuar trabajando con los objetos response y request modificados. - - -Configurar comprobaciones de identidad automáticas --------------------------------------------------- - -De forma predeterminada, ``AuthenticationComponent`` automáticamente forzará que una entidad -esté presente durante el evento ``Controller.startup``. Puede hacer que esta verificación se -aplique durante el evento ``Controller.initialize`` en su lugar:: - - // In your controller's initialize() method. - $this->loadComponent('Authentication.Authentication', [ - 'identityCheckEvent' => 'Controller.initialize', - ]); - -También puede deshabilitar las verificaciones de identidad por completo con la -opción ``requireIdentity`` diff --git a/docs/es/authenticators.rst b/docs/es/authenticators.rst deleted file mode 100644 index e35861f3..00000000 --- a/docs/es/authenticators.rst +++ /dev/null @@ -1,473 +0,0 @@ -Autenticadores -############## - -Los autenticadores se encargan de convertir los datos de la request en operaciones de -autenticación. Utilizan :doc:`/identifiers` para encontrar un -:doc:`/identity-object` conocido. - -Sesión -====== - -Este autenticador comprobará si la sesión contiene datos de usuario o -credenciales. Cuando utilice un autenticador con estado como el ``Form`` listado -más abajo, asegúrese de cargar primero el autenticador de ``Session`` para que una vez -el usuario inicie sesión, los datos del usuario se obtengan de la sesión en las requests -posteriores. - -Opciones de configuración: - -- **sessionKey**: Key para los datos de usuario, por defecto es - ``Auth`` - -Form -==== - -Busca los datos en el cuerpo de la request, generalmente cuando se envía un -formulario vía POST / PUT. - -Opciones de configuración: - -- **loginUrl**: La URL login, puede ser un string o un array de URLs. Por defecto es - ``null`` y se comprobarán todas las páginas. -- **fields**: Array que mapea ``username`` y ``password`` a los campos de - datos POST especificados. -- **urlChecker**: La clase u objeto comprobador de URL. Por defecto es - ``DefaultUrlChecker``. -- **useRegex**: Usar o no expresiones regulares para la coincidencia de URL - Por defecto es ``false``. -- **checkFullUrl**: Comprobar o no la URL completa incluida en la query - string. Útil cuando un formulario login está en un subdominio diferente. Por defecto es - ``false``. Esta opción no funciona bien cuando se conservan los redireccionamientos - no autenticados en la query string. - -Si está creando una API y desea aceptar credenciales via JSON requests asegúrese -de tener el ``BodyParserMiddleware`` aplicado **antes** del -``AuthenticationMiddleware``. - -.. warning:: - Si usa la sintaxis de array para la URL, la URL será generada - por el router de CakePHP. El resultado **podría** diferir de lo que realmente tiene - en la request URI según el manejo de su ruta. ¡Debe considerar que es sensible - a mayúsculas y minúsculas! - -Token -===== - -El token autenticador puede autenticar una request basada en un token que viene -junto con la request en los headers o en los parámetros de ésta. - -Opciones de configuración: - -- **queryParam**: Nombre del parámetro de la query. Configúrelo si desea obtener - el token de los parámetros de la query. -- **header**: Nombre del header. Configúrelo si desea obtener el token - del encabezado. -- **tokenPrefix**: Prefijo opcional del token. - -Un ejemplo de cómo obtener un token de un header o una query string sería:: - - $service->loadAuthenticator('Authentication.Token', [ - 'queryParam' => 'token', - 'header' => 'Authorization', - 'tokenPrefix' => 'Token' - ]); - -Lo anterior leería el parámetro GET del ``token`` o el header ``Authorization`` -siempre que el token estuviera precedido por ``Token`` y un espacio. - -El token siempre se pasará al identificador configurado de la siguiente manera:: - - - [ - 'token' => '{token-value}', - ] - -JWT -=== - -El autenticador JWT obtiene el `JWT token `__ del header o el parámetro -de la query y devuelve el payload directamente o lo pasa -a los identificadores para verificarlos con otra fuente de datos por -ejemplo. - -- **header**: Línea del header para verificar el token. Por defecto es - ``Authorization``. -- **queryParam**: Parámetro de la query para verificar el token. Por defecto - es ``token``. -- **tokenPrefix**: Prefijo del token. Por defecto es ``bearer``. -- **algorithm**: El algoritmo de hash para Firebase JWT. Por defecto es ``'HS256'``. -- **returnPayload**: Retornar o no el payload del token directamente - sin pasar a través de los identificadores. Por defecto es ``true``. -- **secretKey**: Por defecto es ``null`` pero será **requerido** pasar una - key secreta si no está en el contexto de una aplicación CakePHP que la - provee mediante ``Security::salt()``. - -Por defecto, el ``JwtAuthenticator`` usa el algoritmo de key simétrica ``HS256`` -y usa el valor de ``Cake\Utility\Security::salt()`` como key de cifrado. -Para mayor seguridad, se puede utilizar en su lugar el algoritmo de key asimétrica ``RS256``. -Puede generar las keys necesarias para eso de la siguiente manera:: - - # generate private key - openssl genrsa -out config/jwt.key 1024 - # generate public key - openssl rsa -in config/jwt.key -outform PEM -pubout -out config/jwt.pem - -El archivo ``jwt.key`` es la key privada y debe mantenerse a salvo. -El archivo ``jwt.pem`` es la key pública. Este archivo debe usarse cuando necesite verificar tokens -creados por aplicaciones externas, por ejemplo: aplicaciones móviles. - -El siguiente ejemplo le permite identificar al usuario basado en el ``sub`` (asunto) del token -usando el identificador ``JwtSubject`` y configura el ``Authenticator`` para usar la key pública -para la verificación del token. - -Agregue lo siguiente a su clase ``Application``:: - - public function getAuthenticationService(ServerRequestInterface $request): AuthenticationServiceInterface - { - $service = new AuthenticationService(); - // ... - $service->loadIdentifier('Authentication.JwtSubject'); - $service->loadAuthenticator('Authentication.Jwt', [ - 'secretKey' => file_get_contents(CONFIG . '/jwt.pem'), - 'algorithm' => 'RS256', - 'returnPayload' => false - ]); - } - -En su ``UsersController``:: - - use Firebase\JWT\JWT; - - public function login() - { - $result = $this->Authentication->getResult(); - if ($result->isValid()) { - $privateKey = file_get_contents(CONFIG . '/jwt.key'); - $user = $result->getData(); - $payload = [ - 'iss' => 'myapp', - 'sub' => $user->id, - 'exp' => time() + 60, - ]; - $json = [ - 'token' => JWT::encode($payload, $privateKey, 'RS256'), - ]; - } else { - $this->response = $this->response->withStatus(401); - $json = []; - } - $this->set(compact('json')); - $this->viewBuilder()->setOption('serialize', 'json'); - } - -Además de compartir el archivo de key pública con una aplicación externa, puede -distribuirlo a través de un endpoint JWKS configurando su aplicación de la siguiente manera:: - - // config/routes.php - $builder->setExtensions('json'); - $builder->connect('/.well-known/:controller/*', [ - 'action' => 'index', - ], [ - 'controller' => '(jwks)', - ]); // connect /.well-known/jwks.json to JwksController - - // controller/JwksController.php - public function index() - { - $pubKey = file_get_contents(CONFIG . './jwt.pem'); - $res = openssl_pkey_get_public($pubKey); - $detail = openssl_pkey_get_details($res); - $key = [ - 'kty' => 'RSA', - 'alg' => 'RS256', - 'use' => 'sig', - 'e' => JWT::urlsafeB64Encode($detail['rsa']['e']), - 'n' => JWT::urlsafeB64Encode($detail['rsa']['n']), - ]; - $keys['keys'][] = $key; - - $this->viewBuilder()->setClassName('Json'); - $this->set(compact('keys')); - $this->viewBuilder()->setOption('serialize', 'keys'); - } - -Ir a https://datatracker.ietf.org/doc/html/rfc7517 o https://auth0.com/docs/tokens/json-web-tokens/json-web-key-sets para -mas información sobre JWKS. - -HttpBasic -========= - -Ver https://en.wikipedia.org/wiki/Basic_access_authentication - -Opciones de configuración: - -- **realm**: Por defecto es ``$_SERVER['SERVER_NAME']`` sobreescribirlo como - sea necesario. - -HttpDigest -========== - -Ver https://en.wikipedia.org/wiki/Digest_access_authentication - -Opciones de configuración: - -- **realm**: Por defecto es ``null`` -- **qop**: Por defecto es ``auth`` -- **nonce**: Por defecto es ``uniqid(''),`` -- **opaque**: Por defecto es ``null`` - -Cookie Authenticator también conocido como "Remember Me" -======================================================== - -El Autenticador de cookies le permite implementar la función "remember me" -para sus formularios de login. - -Solo asegúrese de que su formulario de login tenga un campo que coincida -con el nombre del campo que está configurado en este authenticator. - -Para cifrar y descifrar su cookie, asegúrese de haber agregado -EncryptedCookieMiddleware a su aplicación *antes* del -AuthenticationMiddleware. - -Opciones de configuración: - -- **rememberMeField**: Por defecto es ``remember_me`` -- **cookie**: Array de opciones cookie: - - - **name**: Nombre de la cookie, por defecto es ``CookieAuth`` - - **expires**: Expiración, por defecto es ``null`` - - **path**: Ruta, por defecto es ``/`` - - **domain**: Dominio, por defecto es un string vacío. - - **secure**: Bool, por defecto es ``false`` - - **httponly**: Bool, por defecto es ``false`` - - **value**: Valor, por defecto es un string vacío. - - **samesite**: String/null El valor para el mismo atributo de sitio. - - Los valores predeterminados para las diversas opciones además de ``cookie.name`` serán - los establecidos para la clase ``Cake\Http\Cookie\Cookie``. Consulte `Cookie::setDefaults() `_ - para conocer los valores predeterminados. - -- **fields**: Array que mapea ``username`` y ``password`` a los campos - de identidad especificados. -- **urlChecker**: La clse u objeto verificador de URL. Por defecto es - ``DefaultUrlChecker``. -- **loginUrl**: The URL de login, string o array de URLs. Por defecto es - ``null`` y todas las páginas serán verificadas. -- **passwordHasher**: Hasher del password a usar para el hash del token. Po defecto - es ``DefaultPasswordHasher::class``. - -Uso ---- - -El autenticador de cookies se puede agregar a un sistema de autenticación basado -en Form & Session. La autenticación de cookies volverá a iniciar sesión automáticamente a los -usuarios después de que expire su sesión durante el tiempo que la cookie sea válida. Si un usuario -se desconecta explícitamente vía ``AuthenticationComponent::logout()``, la cookie de autenticación -**también se destruye**. Una configuración de ejemplo sería:: - - // In Application::getAuthService() - - // Reuse fields in multiple authenticators. - $fields = [ - AbstractIdentifier::CREDENTIAL_USERNAME => 'email', - AbstractIdentifier::CREDENTIAL_PASSWORD => 'password', - ]; - - // Put form authentication first so that users can re-login via - // the login form if necessary. - $service->loadAuthenticator('Authentication.Form', [ - 'loginUrl' => '/users/login', - 'fields' => [ - AbstractIdentifier::CREDENTIAL_USERNAME => 'email', - AbstractIdentifier::CREDENTIAL_PASSWORD => 'password', - ], - ]); - // Then use sessions if they are active. - $service->loadAuthenticator('Authentication.Session'); - - // If the user is on the login page, check for a cookie as well. - $service->loadAuthenticator('Authentication.Cookie', [ - 'fields' => $fields, - 'loginUrl' => '/users/login', - ]); - -También deberá agregar una casilla de verificación a su formulario login para que se creen cookies:: - - // In your login view - Form->control('remember_me', ['type' => 'checkbox']); - -Después de iniciar sesión, si se marcó la casilla de verificación, debería ver una cookie ``CookieAuth`` -en las herramientas de desarrollo de su navegador. La cookie almacena el campo username y un token hash -que se usa para volver a autenticarse más tarde. - -Eventos -======= - -Solo hay un evento que se activa mediante autenticación: -``Authentication.afterIdentify``. - -Si no sabe qué son los eventos y cómo utilizarlos, consulte la -documentación `__. - -El evento ``Authentication.afterIdentify`` es activado por el -``AuthenticationComponent`` despues que una identity fue identificada -satisfactoriamente. - -El evento contiene los siguientes datos: - -- **provider**: Un objeto que implementa - ``\Authentication\Authenticator\AuthenticatorInterface`` -- **identity**: Un objeto que implementa ``\ArrayAccess`` -- **service**: Un objeto que implementa - ``\Authentication\AuthenticationServiceInterface`` - -El asunto del evento será la instancia de controlador actual a la que -está adjunto el AuthenticationComponent. - -Pero el evento solo se activa si el autenticador que se utilizó para identificar -la identity *no* es persistente y *no* es sin estado. La razón de esto es -que el evento se activaría cada vez porque el autenticador de sesión o el token, -por ejemplo, lo activaría cada vez para cada request. - -De los autenticadores incluidos, solo FormAuthenticator hará que se dispare -el evento. Después de eso, el autenticador de sesión proporcionará la identidad. - -Comprobadores de URL -==================== - -Algunos autenticadores como ``Form`` o ``Cookie`` deben ejecutarse solo -en ciertas páginas como la página ``/login``. Esto se puede lograr utilizando -comprobadores de URL. - -De forma predeterminada, se usa un ``DefaultUrlChecker``, que usa URLs string -para comparar con soporte para la verificación de expresiones regulares. - -Opciones de configuración: - -- **useRegex**: Usar o no expresiones regulares para coincidencia - URL. Por defecto es ``false``. -- **checkFullUrl**: Comprobar o no la URL completa. Útil cuando un formulario - login está en un subdominio diferente. Por defecto es ``false``. - -Se puede implementar un verificador de URL personalizado, por ejemplo, -si se necesita soporte para un famework URL específico. En este caso, debe implementarse -la ``Authentication\UrlChecker\UrlCheckerInterface``. - -Para mas detalles de Comprobadores de URL Checkers :doc:`ver esta página de -la documentación `. - -Obtener el Successful Authenticator o el Identifier -=================================================== - -Después de que un usuario ha sido autenticado, es posible que desee inspeccionar o -interactuar con el Authenticator que autenticó correctamente al usuario:: - - // In a controller action - $service = $this->request->getAttribute('authentication'); - - // Will be null on authentication failure, or an authenticator. - $authenticator = $service->getAuthenticationProvider(); - -También puede obtener el identifier que identificó al usuario:: - - // In a controller action - $service = $this->request->getAttribute('authentication'); - - // Will be null on authentication failure, or an identifier. - $identifier = $service->getIdentificationProvider(); - - -Uso de Stateless (sin estado) Authenticators con Stateful (con estado) Authenticators -===================================================================================== - -Cuando se usa ``Token`` o ``HttpBasic``, ``HttpDigest`` con otros autenticadores, -debe recordar que estos autenticadores detendrán la request cuando las credenciales -de autenticación falten o no sean válidas. Esto es necesario ya que estos autenticadores -deben enviar challenge headers específicos en el response:: - - use Authentication\AuthenticationService; - - // Instantiate the service - $service = new AuthenticationService(); - - // Load identifiers - $service->loadIdentifier('Authentication.Password', [ - 'fields' => [ - 'username' => 'email', - 'password' => 'password' - ] - ]); - $service->loadIdentifier('Authentication.Token'); - - // Load the authenticators leaving Basic as the last one. - $service->loadAuthenticator('Authentication.Session'); - $service->loadAuthenticator('Authentication.Form'); - $service->loadAuthenticator('Authentication.HttpBasic'); - -Si desea combinar ``HttpBasic`` o ``HttpDigest`` con otros autenticadores, -tenga en cuenta que estos autenticadores abortarán la request y forzarán -un cuadro de diálogo del navegador. - -Manejo de Errores por no Autenticación -====================================== - -El ``AuthenticationComponent`` generará una excepción cuando los usuarios no estén -autenticados. Puede convertir esta excepción en una redirección utilizando el -``unauthenticatedRedirect`` al configurar el ``AuthenticationService``. - -También puede pasar el URI de destino de la request actual como un parámetro -utilizando la opción ``queryParam``:: - - // In the getAuthenticationService() method of your src/Application.php - - $service = new AuthenticationService(); - - // Configure unauthenticated redirect - $service->setConfig([ - 'unauthenticatedRedirect' => '/users/login', - 'queryParam' => 'redirect', - ]); - -Luego, en el método login del controlador, puede usar ``getLoginRedirect()`` para obtener -del parámetro string de la query el destino de redireccionamiento de manera segura:: - - public function login() - { - $result = $this->Authentication->getResult(); - - // Regardless of POST or GET, redirect if user is logged in - if ($result->isValid()) { - // Use the redirect parameter if present. - $target = $this->Authentication->getLoginRedirect(); - if (!$target) { - $target = ['controller' => 'Pages', 'action' => 'display', 'home']; - } - return $this->redirect($target); - } - } - -Múltiples Flujos de Autenticación -================================= - -En una aplicación que proporciona tanto una API como una interfaz web, -es posible que desee diferentes configuraciones de autenticación en función de -si la request es una API request o no. Por ejemplo, puede utilizar la autenticación JWT -para su API, pero sesiones para su interfaz web. Para admitir este flujo, puede -devolver diferentes servicios de autenticación basados en la ruta URL o cualquier -otro atributo de la request:: - - public function getAuthenticationService( - ServerRequestInterface $request - ): AuthenticationServiceInterface { - $service = new AuthenticationService(); - - // Configuration common to both the API and web goes here. - - if ($request->getParam('prefix') == 'Api') { - // Include API specific authenticators - } else { - // Web UI specific authenticators. - } - - return $service; - } diff --git a/docs/es/conf.py b/docs/es/conf.py deleted file mode 100644 index 4691ece6..00000000 --- a/docs/es/conf.py +++ /dev/null @@ -1,9 +0,0 @@ -import sys, os - -# Append the top level directory of the docs, so we can import from the config dir. -sys.path.insert(0, os.path.abspath('..')) - -# Pull in all the configuration options defined in the global config file.. -from config.all import * - -language = 'es' diff --git a/docs/es/contents.rst b/docs/es/contents.rst deleted file mode 100644 index d48cd778..00000000 --- a/docs/es/contents.rst +++ /dev/null @@ -1,10 +0,0 @@ -Contents -######## - -.. toctree:: - :maxdepth: 2 - :caption: CakePHP Authentication - - /index - /authenticators - /authentication-component diff --git a/docs/es/identifiers.rst b/docs/es/identifiers.rst deleted file mode 100644 index eb13b0b8..00000000 --- a/docs/es/identifiers.rst +++ /dev/null @@ -1,195 +0,0 @@ -Identificadores -############### - -Los Identificadores identificarán a un usuario o servicio en función de la información -que fue extraída de la request por los autenticadores. Los Identificadores -pueden tomar opciones en el método ``loadIdentifier``. Un ejemplo holístico de -el uso del Identificador de Contraseña se ve así:: - - $service->loadIdentifier('Authentication.Password', [ - 'fields' => [ - 'username' => 'email', - 'password' => 'passwd', - ], - 'resolver' => [ - 'className' => 'Authentication.Orm', - 'userModel' => 'Users' - 'finder' => 'active' - ], - 'passwordHasher' => [ - 'className' => 'Authentication.Fallback', - 'hashers' => [ - 'Authentication.Default', - [ - 'className' => 'Authentication.Legacy', - 'hashType' => 'md5' - ], - ] - ] - ]); - -Contraseña -========== - -El identificador de contraseña compara las credenciales suministradas -con una fuente de datos. - -Opciones de configuración: - -- **fields**: Los campos a buscar. Por defecto son - ``['username' => 'username', 'password' => 'password']``. Tambien puede - colocar el ``username`` en un array. Por ejemplo usando - ``['username' => ['username', 'email'], 'password' => 'password']`` - lo que le permitirá hacer coincidir con la columna username o la columna email. -- **resolver**: El resolver de la identidad. Por defecto es - ``Authentication.Orm`` el cual usa el ORM de CakePHP. -- **passwordHasher**: Hasher del password. Por defecto es - ``DefaultPasswordHasher::class``. - -Token -===== - -Comprueba el token suministrado con una fuente de datos. - -Opciones de configuración: - -- **tokenField**: El campo a verificar en la base de datos. Por defecto - es ``token``. -- **dataField**: El campo en los datos suministrados del autenticador. - Por defecto es ``token``. -- **resolver**: El resolver de la identidad. Por defecto es - ``Authentication.Orm`` el cual usa el ORM de CakePHP. - -JWT Subject -=========== - -Comprueba el token JWT suministrado con una fuente de datos. - -Opciones de configuración: - -- **tokenField**: El campo a verificar en la base de datos. Por defecto - es ``id``. -- **dataField**: La clave del payload para obetener el usuario desde el identificador. - Por defecto es ``sub``. -- **resolver**: El resolver de la identidad. Por defecto es - ``Authentication.Orm`` el cual usa el ORM de CakePHP. - -LDAP -==== - -Compara las credenciales suministradas con un servidor LDAP. Este identificador -requiere la extensión PHP LDAP. - -Opciones de configuración: - -- **fields**: Los campos a utilizar. Por defecto son - ``['username' => 'username', 'password' => 'password']``. -- **host**: El FQDN de tu servidor LDAP. -- **port**: El puerto de tu servidor LDAP. Por defecto ``389``. -- **bindDN**: El Distinguished Name de el usuario a autenticar. Debe - ser un callable. Los enlaces anónimos no están soportados. -- **ldap**: El adaptador de la extensión. Por defecto - ``\Authentication\Identifier\Ldap\ExtensionAdapter``. Puedes suministrar un - object/classname personalizado(a) si este(a) implementa el - ``AdapterInterface``. -- **options**: Opciones adicionales del LDAP, como - ``LDAP_OPT_PROTOCOL_VERSION`` o ``LDAP_OPT_NETWORK_TIMEOUT``. Ver - `php.net `__ - para mas opciones válidas. - -Callback -======== - -Le permite usar un callback para la identificación. Esto es útil para -identificadores simples o creación rápida de prototipos. - -Opciones de configuración: - -- **callback**: Por defecto es ``null`` y arrojará una excepción. Se requiere - suministrar un callback válido en esta opción para usar el - autenticador. - -Los identificadores callback pueden retornar ``ArrayAccess|null`` para resultados simples, -o un ``Authentication\Authenticator\Result`` si se quiere enviar mensajes de -error:: - - // Un indentificador callback simple - $authenticationService->loadIdentifier('Authentication.Callback', [ - 'callback' => function($data) { - // Hacer la lógica del identificador - - // Devuelve un array con el usuario identificado o nulo si falla. - if ($result) { - return $result; - } - - return null; - } - ]); - - // Usar un result object para devolver mensajes de error. - $authenticationService->loadIdentifier('Authentication.Callback', [ - 'callback' => function($data) { - // Hacer la lógica del identificador - - if ($result) { - return new Result($result, Result::SUCCESS); - } - - return new Result( - null, - Result::FAILURE_OTHER, - ['message' => 'Removed user.'] - ); - } - ]); - - -Resolvers de identidad -====================== - -Los resolvers de identidades proporcionan adaptadores para diferentes fuentes de datos. -Le permiten controlar en qué identidades de origen buscar. Están separados -de los identificadores para que puedan intercambiarse independientemente del -método del identificador (formulario, jwt, autenticación básica). - -ORM Resolver ------------- - -Es el resolver de dentidad para el ORM de CakePHP. - -Opciones de configuración: - -- **userModel**: El modelo donde están localizadas las indentidades. Por defecto es - ``Users``. -- **finder**: El finder a usar con el modelo. Por defecto es ``all``. - Puede leer mas sobre los finders de los modelos `aquí `__. - -Para usar el resolver ORM se requiere tener ``cakephp/orm`` en su archivo -``composer.json`` (si no estás usando el framework CakePHP completo). - -Escribiendo tu propio resolver ------------------------------- - -Cualquier ORM o fuente de datos puede ser adaptada para trabajar como -autenticación al crear un resolver. Los resolvers deben implementar -``Authentication\Identifier\Resolver\ResolverInterface`` y debe estar -bajo el namespace ``App\Identifier\Resolver``. - -Un resolver puede ser configurado usando las opciones de configuración -de ``resolver``:: - - $service->loadIdentifier('Authentication.Password', [ - 'resolver' => [ - // puede ser un nombre de clase completo: \Some\Other\Custom\Resolver::class - 'className' => 'MyResolver', - // Suministrar opciones adicionales al constructor del resolver. - 'option' => 'value' - ] - ]); - -O usando un setter inyectado:: - - $resolver = new \App\Identifier\Resolver\CustomResolver(); - $identifier = $service->loadIdentifier('Authentication.Password'); - $identifier->setResolver($resolver); diff --git a/docs/es/identity-object.rst b/docs/es/identity-object.rst deleted file mode 100644 index f90e6291..00000000 --- a/docs/es/identity-object.rst +++ /dev/null @@ -1,117 +0,0 @@ -Objetos de identidad -#################### - -Los objetos de identidad son devueltos por el servicio de autenticación y están -disponibles en la request. Las identidades proporcionan un método ``getIdentifier()`` -que se puede llamar para obtener el valor de la id primaria de la identidad con la sesión iniciada. - -La razón por la que este objeto existe es para proporcionar una interfaz que haga -implementaciones/fuentes:: - - // Servicio - $authenticationService - ->getIdentity() - ->getIdentifier() - - // Componente - $this->Authentication - ->getIdentity() - ->getIdentifier(); - - // Request - $this->request - ->getAttribute('identity') - ->getIdentifier(); - -El objeto de identidad proporciona un ArrayAccess y también un método ``get()`` para -acceder a los datos. Se recomienda encarecidamente utilizar el método ``get()`` sobre el -array de acceso porque el método get es consciente del mapeo de campos:: - - $identity->get('email'); - $identity->get('username'); - -El método ``get()`` tambien puede ser type-hinted via archivo IDE meta, ejemplo a traves del -`IdeHelper `__. - -Sin embargo, si lo desea, puede usar el acceso a la propiedad:: - - $identity->email; - $identity->username; - -La clase de objeto de identidad predeterminada se puede configurar para -asignar campos. Esto es muy útil si el identificador de la identidad es -un campo ``id`` no convencional o si desea asignar otros campos a nombres -más genéricos y comunes:: - - $identity = new Identity($data, [ - 'fieldMap' => [ - 'id' => 'uid', - 'username' => 'first_name' - ] - ]); - -Crear su propio objeto de identidad ------------------------------------ - -De forma predeterminada, el plugin de autenticación contendrá los datos de usuario devueltos -en un ``IdentityDecorator`` que representa los métodos y el acceso a las propiedades. -Si desea crear su propio objeto de identidad, su objeto debe implementar el -``IdentityInterface``. - -Implementar IdentityInterface en su clase User ----------------------------------------------- - -Si desea continuar usando su clase User existente con este plugin, -puede implementar ``Authentication\IdentityInterface``:: - - namespace App\Model\Entity; - - use Authentication\IdentityInterface; - use Cake\ORM\Entity; - - class User extends Entity implements IdentityInterface - { - /** - * Authentication\IdentityInterface method - */ - public function getIdentifier() - { - return $this->id; - } - - /** - * Authentication\IdentityInterface method - */ - public function getOriginalData() - { - return $this; - } - - // Otros métodos - } - -Usar un Decorador de Identidad Personalizado --------------------------------------------- - -Si sus identificadores no pueden modificar los objetos resultantes -al implementar ``IdentityInterface``, puede implementar un decorador -personalizado que implemente la interfaz requerida:: - - // Puede usar un callable... - $identityResolver = function ($data) { - return new MyCustomIdentity($data); - }; - - //...o un nombre de clase para establecer el contenedor de identidad. - $identityResolver = MyCustomIdentity::class; - - // Luego pasarlo a la configuración del servicio - $service = new AuthenticationService([ - 'identityClass' => $identityResolver, - 'identifiers' => [ - 'Authentication.Password' - ], - 'authenticators' => [ - 'Authentication.Form' - ] - ]); diff --git a/docs/es/index.rst b/docs/es/index.rst deleted file mode 100644 index 3c86e3a6..00000000 --- a/docs/es/index.rst +++ /dev/null @@ -1,224 +0,0 @@ -Inicio Rápido -############# - -Instale el plugin con `composer `_ desde el directorio ROOT -del Proyecto CakePHP (donde está localizado el archivo **composer.json**) - -.. code-block:: bash - - php composer.phar require "cakephp/authentication:^2.0" - -Carge el plugin agregando la siguiente declaración en ``src/Application.php``:: - - public function bootstrap(): void - { - parent::bootstrap(); - - $this->addPlugin('Authentication'); - } - - -Empezando -========= - -El plugin authentication se integra con su aplicación como un `middleware `_. También, se -puede utilizar como un componente para simplificar el acceso no autenticado. Primero -aplique el middleware. En **src/Application.php**, agregue las siguientes importaciones -de clase:: - - use Authentication\AuthenticationService; - use Authentication\AuthenticationServiceInterface; - use Authentication\AuthenticationServiceProviderInterface; - use Authentication\Identifier\AbstractIdentifier; - use Authentication\Middleware\AuthenticationMiddleware; - use Cake\Http\MiddlewareQueue; - use Cake\Routing\Router; - use Psr\Http\Message\ServerRequestInterface; - -A continuación, agregue ``AuthenticationServiceProviderInterface`` a las interfaces implementadas -en su aplicación:: - - class Application extends BaseApplication implements AuthenticationServiceProviderInterface - -Luego agregue ``AuthenticationMiddleware`` a la cola de middleware en la función ``middleware()``:: - - $middlewareQueue->add(new AuthenticationMiddleware($this)); - -.. note:: - Asegúrese de agregar ``AuthenticationMiddleware`` antes de - ``AuthorizationMiddleware`` si tiene ambos, y después de - ``RoutingMiddleware``. - -``AuthenticationMiddleware`` llamará a un método hook en su aplicación cuando -comience a manejar la solicitud. Este método hook permite que su aplicación defina -el ``AuthenticationService`` que quiere usar. Agregue el siguiente método a su -**src/Application.php**:: - - /** - * Returns a service provider instance. - * - * @param \Psr\Http\Message\ServerRequestInterface $request Request - * @return \Authentication\AuthenticationServiceInterface - */ - public function getAuthenticationService(ServerRequestInterface $request): AuthenticationServiceInterface - { - $service = new AuthenticationService(); - - // Define where users should be redirected to when they are not authenticated - $service->setConfig([ - 'unauthenticatedRedirect' => Router::url([ - 'prefix' => false, - 'plugin' => null, - 'controller' => 'Users', - 'action' => 'login', - ]), - 'queryParam' => 'redirect', - ]); - - $fields = [ - AbstractIdentifier::CREDENTIAL_USERNAME => 'email', - AbstractIdentifier::CREDENTIAL_PASSWORD => 'password' - ]; - // Load the authenticators. Session should be first. - $service->loadAuthenticator('Authentication.Session'); - $service->loadAuthenticator('Authentication.Form', [ - 'fields' => $fields, - 'loginUrl' => Router::url([ - 'prefix' => false, - 'plugin' => null, - 'controller' => 'Users', - 'action' => 'login', - ]), - ]); - - // Load identifiers - $service->loadIdentifier('Authentication.Password', compact('fields')); - - return $service; - } - -En el bloque anterior, primero, configuramos qué hacer con los usuarios cuando no están autenticados. -A continuación, adjuntamos la ``Session`` y el ``Form`` :doc:`/authenticators` que definen los -mecanismos que utilizará nuestra aplicación para autenticar usuarios. ``Session`` nos permite identificar a los -usuarios en función de los datos de la sesión, mientras que ``Form`` nos permite gestionar un -formulario de inicio de sesión en el ``loginUrl``. Finalmente adjuntamos un :doc:`identifier -` para convertir las credenciales que los usuarios nos darán en un -:doc:`identity ` que representa nuestro usuario registrado. - -Si uno de los autenticadores configurados pudo validar las credenciales, -el middleware agregará el servicio de autenticación al objeto request como un -`attribute `_. - -A continuación, en su ``AppController`` cargue el :doc:`/authentication-component`:: - - // in src/Controller/AppController.php - public function initialize() - { - parent::initialize(); - - $this->loadComponent('Authentication.Authentication'); - } - -De forma predeterminada, el componente requerirá un usuario autenticado para **todas** las acciones. -Puede deshabilitar este comportamiento en controladores específicos usando -``allowUnauthenticated()``:: - - // in a controller beforeFilter or initialize - // Make view and index not require a logged in user. - $this->Authentication->allowUnauthenticated(['view', 'index']); - -Creación de una acción Login -============================ - -Una vez que haya aplicado el middleware a su aplicación, necesitará una forma para que los -usuarios inicien sesión. Primero genere un modelo y un controlador de usuarios con ``bake``: - -.. code-block:: shell - - bin/cake bake model Users - bin/cake bake controller Users - -Luego, agregue una acción login a su ``UsersController``. Debería verse -así:: - - // in src/Controller/UsersController.php - public function login() - { - $result = $this->Authentication->getResult(); - // If the user is logged in send them away. - if ($result->isValid()) { - $target = $this->Authentication->getLoginRedirect() ?? '/home'; - return $this->redirect($target); - } - if ($this->request->is('post')) { - $this->Flash->error('Invalid username or password'); - } - } - -Asegúrese de permitir el acceso a la acción ``login`` en su contralador en -``beforeFilter()`` callback como se menciona en la sección anterior, así -los usuarios no autenticados puedan acceder a ella:: - - // in src/Controller/UsersController.php - public function beforeFilter(\Cake\Event\EventInterface $event) - { - parent::beforeFilter($event); - - $this->Authentication->allowUnauthenticated(['login']); - } - -A continuación, agregaremos un template para nuestro formulario login:: - - // in templates/Users/login.php -
- Form->create() ?> -
- - Form->control('email') ?> - Form->control('password') ?> -
- Form->button(__('Login')); ?> - Form->end() ?> -
- -Luego agregue una acción logout:: - - // in src/Controller/UsersController.php - public function logout() - { - $this->Authentication->logout(); - return $this->redirect(['controller' => 'Users', 'action' => 'login']); - } - -No necesitamos un template para nuestra acción logout ya que redirigimos al final. - -Adición de hash de contraseña -============================= - -Para iniciar sesión, sus usuarios deberán tener contraseñas hash. Puede aplicar hash -a las contraseñas automáticamente cuando los usuarios actualizan su contraseña mediante un método -entity setter:: - - // in src/Model/Entity/User.php - use Authentication\PasswordHasher\DefaultPasswordHasher; - - class User extends Entity - { - // ... other methods - - // Automatically hash passwords when they are changed. - protected function _setPassword(string $password) - { - $hasher = new DefaultPasswordHasher(); - return $hasher->hash($password); - } - } - -Ahora debería poder ir a ``/users/add`` y registrar un nuevo usuario. Una vez registrado, -puede ir a ``/users/login`` iniciar sesión con su usuario recién creado. - -Otras lecturas -============== - -* :doc:`/authenticators` -* :doc:`/authentication-component` diff --git a/docs/es/middleware.rst b/docs/es/middleware.rst deleted file mode 100644 index c94fd928..00000000 --- a/docs/es/middleware.rst +++ /dev/null @@ -1,62 +0,0 @@ -Middleware -########## - -``AuthenticationMiddleware`` es el corazón del plugin de autenticación. -Intercepta cada solicitud a su aplicación e intenta autenticar a un usuario -con uno de los autenticadores. Cada autenticador se prueba en orden hasta -que se autentica un usuario o no se puede encontrar ningún usuario. Los atributos -de ``authentication``, ``identity`` y ``authenticationResult`` se establecen en la -solicitud que contiene la identidad si se encontró una y el objeto result de la -autenticación que puede contener errores adicionales proporcionados por los autenticadores. - -Al final de cada solicitud, la ``identity`` se conserva en cada autenticador con estado, -como el autenticador ``Session``. - -Configuración -============= - -Toda la configuración del middleware se realiza en el ``AuthenticationService``. -En este servicio puede utilizar las siguientes opciones de configuración: - -- ``identityClass`` - El nombre de clase de identidad o un constructor de identidad callable. -- ``identityAttribute`` - El atributo de solicitud utilizado para almacenar la identidad. - Por defecto ``identity``. -- ``unauthenticatedRedirect`` - La URL para redirigir los errores no autenticados. -- ``queryParam`` - El nombre del parámetro del string de consulta que contiene - la URL previamente bloqueada en caso de redireccionamiento no autenticado, o nulo - para deshabilitar la adición de la URL denegada. Por defecto ``null``. - - -Configuración de Múltiples Configuraciones de Autenticación -=========================================================== - -Si su aplicación requiere diferentes configuraciones de autenticación para diferentes partes -de la aplicación, por ejemplo, la API y la interfaz de usuario web. Puede hacerlo utilizando -lógica condicional en el método hook ``getAuthenticationService()`` de sus aplicaciones. Al -inspeccionar el objeto request, puede configurar la autenticación de manera adecuada:: - - public function getAuthenticationService(ServerRequestInterface $request): AuthenticationServiceInterface - { - $path = $request->getPath(); - - $service = new AuthenticationService(); - if (strpos($path, '/api') === 0) { - // Acepta solo tokens de API - $service->loadAuthenticator('Authentication.Token'); - $service->loadIdentifier('Authentication.Token'); - - return $service; - } - - // Autenticación web - // Soporte de sessions y formulario login. - $service->loadAuthenticator('Authentication.Session'); - $service->loadAuthenticator('Authentication.Form'); - - $service->loadIdentifier('Authentication.Password'); - - return $service; - } - -Si bien el ejemplo anterior usa un prefijo de ruta, puede aplicar una lógica similar -al subdominio, dominio o cualquier otro encabezado o atributo presente en la solicitud. diff --git a/docs/es/password-hashers.rst b/docs/es/password-hashers.rst deleted file mode 100644 index 0486000f..00000000 --- a/docs/es/password-hashers.rst +++ /dev/null @@ -1,80 +0,0 @@ -Hashers de Contraseña -##################### - -Por defecto -=========== - -Usando la constante php ``PASSWORD_DEFAULT`` para el método de encriptación. -El tipo de hash por defecto es ``bcrypt``. - -Ver `la documentación -php `__ -para obtener más información sobre bcrypt y el hash de contraseñas de PHP. - -Las opciones de configuración de este adaptador son: - -- **hashType**: Algoritmo hash a usar. Los valores válidos son los admitidos - por ``$algo`` argumento de ``password_hash()``. Por defecto es - ``PASSWORD_DEFAULT`` -- **hashOptions**: Array asociativo de opciones. Revisa las opciones soportadas - para cada tipo de hash en el manual PHP. Por defecto es un array vacío. - -Legacy -====== - -Este es un hasher de contraseñas para aplicaciones que fueron migradas -de CakePHP2. - -Fallback -======== - -El hasher de contraseña fallback le permite configurar varios hashers y los -comprobará secuencialmente. Esto permite a los usuarios iniciar sesión con un -tipo de hash antiguo hasta que se restablezca su contraseña y se actualice a un nuevo hash. - -Actualización de algoritmos hash -================================ - -CakePHP proporciona una forma limpia de migrar las contraseñas de sus -usuarios de un algoritmo a otro, esto se logra a través de la clase -``FallbackPasswordHasher``. Suponiendo que desea migrar de una contraseña -Legacy a el hasher bcrypt predeterminado, puede configurar el hasher fallback -de la siguiente manera:: - - $service->loadIdentifier('Authentication.Password', [ - // Otras opciones de configuración - 'passwordHasher' => [ - 'className' => 'Authentication.Fallback', - 'hashers' => [ - 'Authentication.Default', - [ - 'className' => 'Authentication.Legacy', - 'hashType' => 'md5', - 'salt' => false // desactiva el uso predeterminado de salt - ], - ] - ] - ]); - -Luego, en su acción login, puede usar el servicio de autenticación para acceder -al identificador del ``Password`` y verificar si la contraseña del usuario -actual debe actualizarse:: - - public function login() - { - $authentication = $this->request->getAttribute('authentication'); - $result = $authentication->getResult(); - - // independientemente de si es POST o GET, redirige si el usuario ha iniciado sesión - if ($result->isValid()) { - // Suponiendo que está utilizando el identificador `Password`. - if ($authentication->identifiers()->get('Password')->needsPasswordRehash()) { - // El rehash ocurre al guardar. - $user = $this->Users->get($this->Auth->user('id')); - $user->password = $this->request->getData('password'); - $this->Users->save($user); - } - - // Redirige o muestra una plantilla.. - } - } diff --git a/docs/fr/authentication-component.rst b/docs/fr/authentication-component.rst deleted file mode 100644 index 52ba149b..00000000 --- a/docs/fr/authentication-component.rst +++ /dev/null @@ -1,111 +0,0 @@ -Composant Authentication -======================== - -Vous pouvez utiliser ``AuthenticationComponent`` pour accéder au résultat de -l'authentification, obtenir l'identité de l'utilisateur et déconnecter -l'utilisateur. Chargez le composant dans votre méthode -``AppController::initialize()`` comme n'importe quel autre composant:: - - $this->loadComponent('Authentication.Authentication', [ - 'logoutRedirect' => '/users/login' // false par défaut - ]); - -Une fois chargé, ``AuthenticationComponent`` exigera la présence d'un -utilisateur authentifié pour toutes les actions, mais ne réalise pas d'autres -contrôles d'accès. Vous pouvez désactiver cette vérification pour certaines -actions en utilisant ``allowUnauthenticated()``:: - - // Dans la méthode beforeFilter de votre contrôleur. - $this->Authentication->allowUnauthenticated(['view']); - -Accéder à l'utiliseur connecté ------------------------------- - -Vous pouvez obtenir les données d'identité de l'utilisateur authentifié à partir -du composant Authentication:: - - $user = $this->Authentication->getIdentity(); - -Vous pouvez aussi obtenir l'identité directement depuis l'instance de la -requête:: - - $user = $request->getAttribute('identity'); - -Vérifier le statut de connexion -------------------------------- - -Vous pouvez vérifier si le processus d'authentification s'est bien déroulé en -accédant à l'objet résultat:: - - // En utilisant le composant Authentication - $result = $this->Authentication->getResult(); - - // En utilisant l'objet requête - $result = $request->getAttribute('authentication')->getResult(); - - if ($result->isValid()) { - $user = $request->getAttribute('identity'); - } else { - $this->log($result->getStatus()); - $this->log($result->getErrors()); - } - -Le statut des objets result sets renvoyé par ``getStatus()`` correspondra à -l'une de ces constantes dans l'objet Result: - -* ``ResultInterface::SUCCESS``, en cas de succès. -* ``ResultInterface::FAILURE_IDENTITY_NOT_FOUND``, lorsque l'identité n'a pas été trouvée. -* ``ResultInterface::FAILURE_CREDENTIALS_INVALID``, lorsque les identifiants de connexion sont invalides. -* ``ResultInterface::FAILURE_CREDENTIALS_MISSING``, lorsque les identifiants sont absents de la requête. -* ``ResultInterface::FAILURE_OTHER``, en cas d'échec pour toute autre raison. - -Le tableau d'erreur renvoyé par ``getErrors()`` contient des informations -**supplémentaires** venant du système spécifique qui a tenté l'authentification. -Par exemple LDAP ou OAuth y placeraient les erreurs spécifiques à leurs -implémentations pour faciliter le logging et déboguer le problème. Mais la -plupart des *authenticators* n'insèrent rien à cet endroit. - -Déconnecter l'utilisateur -------------------------- - -Pour déconnecter un utilisateur, exécutez simplement:: - - $this->Authentication->logout(); - -Si vous avez défini une configuration pour le paramètre ``logoutRedirect``, -``Authentication::logout()`` renverra cette valeur, sinon il renverra ``false``. -Dans tous les cas, il ne fera aucune redirection. - -Au choix, vous pouvez déconnecter l'utilisateur en utilisant le service plutôt -que le composant:: - - $return = $request->getAttribute('authentication')->clearIdentity($request, $response); - -Le résultat renvoyé contiendra un tableau tel que celui-ci:: - - [ - 'response' => object(Cake\Http\Response) { ... }, - 'request' => object(Cake\Http\ServerRequest) { ... }, - ] - -.. note:: - Cela renverra un tableau contenant les objets requête et réponse. Puisque - les deux sont immuables, vous aurez de nouveaux objets en retour. À partir - de ce pointe, selon le contexte dans lequel vous travaillez, vous devrez - utiliser ces instances si vous voulez continuer à travailler avec les objets - requête et réponse modifiés. - -Configurer les Vérifications d'Identité Automatiques ----------------------------------------------------- - -Par défaut, ``AuthenticationComponent`` imposera qu'une identité soit présente -pendant l'événement ``Controller.startup``. Vous pouvez faire appliquer cette -vérification plutôt dans l'événement ``Controller.initialize``:: - - // Dans la méthode initialize() de votre contrôleur. - $this->loadComponent('Authentication.Authentication', [ - 'identityCheckEvent' => 'Controller.initialize', - ]); - -Vous pouvez aussi désactiver entièrement ces vérifications d'identité avec -l'option ``requireIdentity``. diff --git a/docs/fr/authenticators.rst b/docs/fr/authenticators.rst deleted file mode 100644 index 5b0f07d3..00000000 --- a/docs/fr/authenticators.rst +++ /dev/null @@ -1,555 +0,0 @@ -Authentificateurs -################# - -Les authentificateurs (*authenticators*) sont chargés de convertir les -données de la requête en opérations d'authentification. Ils s'appuient sur les -:doc:`/identifiers` pour trouver un :doc:`/identity-object` connu. - -Session -======= - -Cet authentificateur va vérifier si la session contient des informations -utilisateur ou des identifiants. Quand vous utilisez des authentificateurs à -états listés ci-dessous, tels que ``Form``, assurez-vous de charger d'abord -l'authentificateur ``Session``, de manière à ce qu'une fois que l'utilisateur -est connecté, ses données soient récupérées depuis la session elle-même lors des -requêtes suivantes. - -Les options de configuration: - -- **sessionKey**: La clé de session pour les données de l'utilisateur, par - défaut ``Auth``. - -Form -==== - -Consulte les données dans le corps de la requête, habituellement quand un -formulaire a été soumis via POST / PUT. - -Options de configuration: - -- **loginUrl**: L'URL de connexion, chaîne de texte ou tableau d'URLs. La - valeur par défaut est ``null`` et toutes les pages seront vérifiées. -- **fields**: Tableau qui mappe ``username`` et ``password`` aux champs de - données POST spécifiés. -- **urlChecker**: La classe ou l'instance de vérification d'URL. Par défaut - ``DefaultUrlChecker``. -- **useRegex**: Indique si l' *URL matching* doit ou non utiliser des - expressions régulières. Par défaut ``false``. -- **checkFullUrl**: Indique s'il faut vérifier l'URL entière, y compris la - *query string*. Utile quand le formulaire de connexion est dans un - sous-domaine différent. Par défaut ``false``. Cette option ne fonctionne pas - correctement lorsqu'on conserve des redirections en cas de - non-authentification dans la query string. - -Si vous construisez une API et que vous voulez accepter les identifiants envoyés -dans une requête JSON, veillez à ce que ``BodyParserMiddleware`` soit défini -**avant** le ``AuthenticationMiddleware``. - -.. warning:: - Si vous utilisez la syntaxe en tableau pour l'URL, l'URL sera générée par le - routeur de CakePHP. Selon la gestion des routes, **il se peut** que le résultat soit différent de ce que - vous avez en réalité dans l'URI de la requête. Dès lors, considérez cela - comme sensible à la casse! - -Token -===== - -L'authentificateur par jeton d'accès (*token*) peut authentifier une -requête en se fondant sur un jeton d'accès qui est transmis avec la requête, -que ce soit dans les en-têtes ou dans les paramètres de cette dernière. - -Options de configuration: - -- **queryParam**: Nom du paramètre dans la requête. Configurez-le si vous - voulez récupérer le jeton d'accès depuis les paramètres de la requête. -- **header**: Nom de l'en-tête. Configurez-le si vous voulez récupérer le jeton - d'accès depuis l'en-tête. -- **tokenPrefix**: Le préfixe du jeton d'accès (optionnel). - -Un exemple de récupération d'un jeton d'accès à partir d'une en-tête ou d'une -query string pourrait être:: - - $service->loadAuthenticator('Authentication.Token', [ - 'queryParam' => 'token', - 'header' => 'Authorization', - 'tokenPrefix' => 'Token' - ]); - -Ce qui précède lirait le paramètre GET ``token`` ou l'en-tête ``Authorization``, -dès lors que le jeton d'accès serait précédé par ``Token`` et d'une espace. - -Le jeton d'accès sera toujours passé de la façon suivante à l'identificateur -configuré:: - - [ - 'token' => '{token-value}', - ] - -JWT -=== - -L'authentificateur JWT obtient le `jeton d'accès JWT `__ à -partir de l'en-tête ou du paralètre de la requête et, selon le cas, renvoie la -payload directement la passe aux identificateurs pour la confronter à une autre -source de données, par exemple. - -- **header**: La ligne d'en-tête dans laquelle chercher le jeton d'accès. La - valeur par défaut est ``Authorization``. -- **queryParam**: Le paramètre de requête dans lequel chercher le jeton - d'accès. La valeur par défaut est ``token``. -- **tokenPrefix**: Le préfixe du jeton d'accès. La valeur par défaut est - ``bearer``. -- **algorithm**: L'algorithme de hachage pour Firebase JWT. La valeur par défaut - est ``'HS256'``. -- **returnPayload**: Renvoyer ou non la payload du jeton d'accès directement - sans passer par les identificateurs. La valeur par défaut est ``true``. -- **secretKey**: La valeur par défaut est ``null`` mais vous **devez - impérativement** transmettre une clé secrète si vous n'êtes pas dans le - contexte d'une application CakePHP qui le fournit déjà par - ``Security::salt()``. -- **jwks**: Par défaut ``null``. Tableau associatif avec une clé ``'keys'``. - S'il est fourni, il sera utilisé à la place de ``secret key``. - -Pour utiliser le ``JwtAuthenticator``, vous devez ajouter à votre application la -bibliothèque `firebase/php-jwt `__ v6.2 ou -supérieure. - -Par défaut, le ``JwtAuthenticator`` utilise l'algorithme de clé symétrique -``HS256`` et utilise la valeur de ``Cake\Utility\Security::salt()`` comme clé de -cryptage. -Pour plus de sécurité, il est possible d'utiliser à la place l'algorithme de clé -asymétrique ``RS256``. Vous pouvez générer les clés nécessaires comme suit:: - - # générer la clé privée - openssl genrsa -out config/jwt.key 1024 - # générer la clé publique - openssl rsa -in config/jwt.key -outform PEM -pubout -out config/jwt.pem - -Le fichier ``jwt.key`` est la clé privée et doit être gardé en sécurité. Le -fichier ``jwt.pem`` est la clé publique. Ce fichier devrait être utilisé quand -vous avez besoin de vérifier les jetons d'accès créés par une application -externe, par exemple les applications mobiles. - -L'exemple suivant vous permet d'identifier l'utilisateur à partir du ``sub`` -(*subject*) du jeton d'accès en utilisant l'identificateur ``JwtSubject``, et -configure l'\ ``Authenticator`` pour utiliser une clé publique lors de la -vérification du jeton d'accès. - -Ajoutez ce qui suit dans votre classe ``Application``:: - - public function getAuthenticationService(ServerRequestInterface $request): AuthenticationServiceInterface - { - $service = new AuthenticationService(); - // ... - $service->loadIdentifier('Authentication.JwtSubject'); - $service->loadAuthenticator('Authentication.Jwt', [ - 'secretKey' => file_get_contents(CONFIG . '/jwt.pem'), - 'algorithm' => 'RS256', - 'returnPayload' => false - ]); - } - -Dans votre ``UsersController``:: - - use Firebase\JWT\JWT; - - public function login() - { - $result = $this->Authentication->getResult(); - if ($result->isValid()) { - $privateKey = file_get_contents(CONFIG . '/jwt.key'); - $user = $result->getData(); - $payload = [ - 'iss' => 'myapp', - 'sub' => $user->id, - 'exp' => time() + 60, - ]; - $json = [ - 'token' => JWT::encode($payload, $privateKey, 'RS256'), - ]; - } else { - $this->response = $this->response->withStatus(401); - $json = []; - } - $this->set(compact('json')); - $this->viewBuilder()->setOption('serialize', 'json'); - } - -Cela marche aussi en utilisant un JWKS récupéré depuis un terminal JWKS -extérieur:: - - // Application.php - public function getAuthenticationService(ServerRequestInterface $request): AuthenticationServiceInterface - { - $service = new AuthenticationService(); - // ... - $service->loadIdentifier('Authentication.JwtSubject'); - - $jwksUrl = 'https://appleid.apple.com/auth/keys'; - - // Ensemble de clés. La clé "keys" est nécessaire. De plus les clés - // nécessitent une clé "alg". - // Ajoutez-la manuellement à votre tableau JWK si elle n'existe pas déjà. - $jsonWebKeySet = Cache::remember('jwks-' . md5($jwksUrl), function () use ($jwksUrl) { - $http = new Client(); - $response = $http->get($jwksUrl); - return $response->getJson(); - }); - - $service->loadAuthenticator('Authentication.Jwt', [ - 'jwks' => $jsonWebKeySet, - 'returnPayload' => false - ]); - } - -La ressource JWKS renverra la plupart du temps le même ensemble de clés. -Les applications devraient mettre ces ressources en cache, mais elles doivent -aussi être préparées à gérer la rotation des clés de chiffrement. - -.. warning:: - - Les applications doivent choisir une durée de vie du cache qui fasse un - compromis entre la performance et la sécurité. - C'est particulièrement important dans les situations où une clé privée - serait compromise. - -Au lieu de partager votre clé publique avec des applications externes, vous -pouvez les distribuer via un point terminal JWKS en configurant votre -application comme suit:: - - // config/routes.php - $builder->setExtensions('json'); - $builder->connect('/.well-known/:controller/*', [ - 'action' => 'index', - ], [ - 'controller' => '(jwks)', - ]); // connect /.well-known/jwks.json to JwksController - - // controller/JwksController.php - public function index() - { - $pubKey = file_get_contents(CONFIG . './jwt.pem'); - $res = openssl_pkey_get_public($pubKey); - $detail = openssl_pkey_get_details($res); - $key = [ - 'kty' => 'RSA', - 'alg' => 'RS256', - 'use' => 'sig', - 'e' => JWT::urlsafeB64Encode($detail['rsa']['e']), - 'n' => JWT::urlsafeB64Encode($detail['rsa']['n']), - ]; - $keys['keys'][] = $key; - - $this->viewBuilder()->setClassName('Json'); - $this->set(compact('keys')); - $this->viewBuilder()->setOption('serialize', 'keys'); - } - -Consultez https://datatracker.ietf.org/doc/html/rfc7517 ou -https://auth0.com/docs/tokens/json-web-tokens/json-web-key-sets pour plus -d'informations à propos de JWKS. - -HttpBasic -========= - -Cf. https://en.wikipedia.org/wiki/Basic_access_authentication - -.. note:: - - Cet authentificateur arrêtera la requête si les identifiants - d'authentification sont absents ou invalides. - -Options de configuration: - -- **realm**: Par défaut ``$_SERVER['SERVER_NAME']``. Remplacez-le en tant que - de besoin. - -HttpDigest -========== - -Cf. https://en.wikipedia.org/wiki/Digest_access_authentication - -Options de configuration: - -- **realm**: Par défaut ``null`` -- **qop**: Par défaut ``auth`` -- **nonce**: Par défaut ``uniqid(''),`` -- **opaque**: Par défaut ``null`` - -Authentificateur Cookie, alias "Se Souvenir de Moi" -=================================================== - -L'authentificateur ``Cookie`` vous permet d'implémenter la fonctionnalité "se -souvenir de moi" dans vos formulaires de connexion. - -Assurez-vous simplement que votre formulaire a un champ qui correspond au nom de -champ configuré dans cet authentificateur. - -Pour crypter et décrypter votre cookie assurez-vous d'avoir ajouté -l'EncryptedCookieMiddleware à votre application *avant* -l'AuthenticationMiddleware. - -Options de configuration: - -- **rememberMeField**: Par défaut ``remember_me`` -- **cookie**: Tableau d'options du cookie: - - - **name**: Nom du cookie, par défaut ``CookieAuth`` - - **expires**: Expiration, par défaut ``null`` - - **path**: Chemin, par défaut ``/`` - - **domain**: Domaine, par défaut une chaîne vide. - - **secure**: Booléen, par défaut ``false`` - - **httponly**: Booléen, par défaut ``false`` - - **value**: Valeur, par défaut une chaîne vide. - - **samesite**: String/null La valeur de l'attribut samesite. - - Les valeurs par défaut des diverses options, à part ``cookie.name``, seront - celles définies pour la classe ``Cake\Http\Cookie\Cookie``. Référez-vous à - `Cookie::setDefaults() `_ - pour les valeurs par défaut. - -- **fields**: Tableau qui mappe ``username`` et ``password`` aux champs - d'identité spécifiés. -- **urlChecker**: La classe ou l'instance du vérificateur d'URL. Par défaut - ``DefaultUrlChecker``. -- **loginUrl**: L'URL de connexion, chaîne ou tableau d'URLs. Par défaut - ``null`` et toutes les pages seront vérifiées. -- **passwordHasher**: Le hacheur de mot de passe à utiliser pour le hachage du - jeton d'accès. Par défaut ``DefaultPasswordHasher::class``. -- **salt**: Si ``false``, aucun grain de sel n'est utilisé. Si c'est une chaîne - de caractères, cette chaîne est utilisée comme grain de sel. Si ``true``, - c'est la valeur par défaut Security.salt qui sera utilisée. ``true`` Par - défaut. Quand un grain de sel est utilisé, la valeur du cookie contiendra - `hash(username + password + hmac(username + password, salt))`. Cela contribue - à durcir les jetons contre de possible failles de la base de données et - active l'invalidation des cookies à chaque rotation du grain de sel. - -Utilisation ------------ - -L'authentificateur par cookie peut compléter un système d'authentification basé -sur Form & Session. L'authentificateur Cookie reconnectera automatiquement les -utilisateurs après que leur session aura expiré, aussi longtemps que le cookie -restera valide. Si un utilisateur est explicitement déconnecté via -``AuthenticationComponent::logout()``, l'authentificateur cookie est **lui aussi -détruit**. Un exemple de configuration serait:: - - // Dans Application::getAuthService() - - // Réutiliser les champs dans plusieurs authentificateurs. - $fields = [ - AbstractIdentifier::CREDENTIAL_USERNAME => 'email', - AbstractIdentifier::CREDENTIAL_PASSWORD => 'password', - ]; - - // Placer l'authentification par formulaire en premier de façon à ce que les - // utilisateurs puissent se reconnecter via le formulaire si besoin. - $service->loadAuthenticator('Authentication.Form', [ - 'loginUrl' => '/users/login', - 'fields' => [ - AbstractIdentifier::CREDENTIAL_USERNAME => 'email', - AbstractIdentifier::CREDENTIAL_PASSWORD => 'password', - ], - ]); - // Ensuite utiliser les sessions si elles sont actives. - $service->loadAuthenticator('Authentication.Session'); - - // Si l'utilisateur est sur la page de connexion, vérifier aussi un éventuel cookie. - $service->loadAuthenticator('Authentication.Cookie', [ - 'fields' => $fields, - 'loginUrl' => '/users/login', - ]); - -Vous aurez aussi besoin d'ajouter une case à cocher à votre formulaire pour -générer la création de cookie:: - - // Dans la vue de votre formulaire de connesion - Form->control('remember_me', ['type' => 'checkbox']); - -Après la connexion, si votre case à cocher a été cochée, vous devriez voir un -cookie ``CookieAuth`` dans les outils de développement de votre navigateur. Le -cookie enregistre l'identifiant de l'utilisateur (*username*) et un jeton -d'accès haché qui est utilisé ultérieurement pour se réauthentifier. - -Événements -========== - -Il n'y a qu'un événement déclenché par l'authentification: -``Authentication.afterIdentify``. - -Si vous ne savez pas ce que sont les événements ou comment les utiliser, -`consultez la documentation `__. - -L'événement ``Authentication.afterIdentify`` est lancé par -l'\ ``AuthenticationComponent`` après qu'une identité a été identifiée avec -succès. - -L'événement contient les informations suivantes: - -- **provider**: Un objet qui implémente - ``\Authentication\Authenticator\AuthenticatorInterface`` -- **identity**: Un objet qui implémente ``\ArrayAccess`` -- **service**: Un objet qui implémente - ``\Authentication\AuthenticationServiceInterface`` - -Le sujet de l'événement sera l'instance du contrôleur en cours auquel -l'AuthenticationComponent est attaché. - -Mais l'événement ne sera déclenché que si l'authentificateur qui a été utilisé -pour identifier l'identité n'est *ni* persistant *ni* stateless. La raison en -est que sinon, l'évenement serait déclenché à chaque fois parce que -les authentificateurs par session ou par jeton, par exemple, le lanceraient -systématiquement à chaque requête. - -Parmi les authentificateurs fournis, seul FormAuthenticator entraînera le -déclenchement de l'événement. Par la suite, l'authentificateur par session -fournira l'identité. - -Vérificateurs d'URL -=================== - -Certains authentificateurs comme ``Form`` ou ``Cookie`` ne devraient être -exécutés que sur certaines pages, telles que la page ``/login``. Cela peut être -obtenu grâce aux vérificateurs d'URL. - -Par défaut, CakePHP utilise un ``DefaultUrlChecker`` qui confronte le texte des -URLs à un moteur d'expressions régulières. - -Options de configuration: - -- **useRegex**: S'il faut ou non utiliser des expressions régulières pour la - l'analyse des URL. La valeur par défaut est ``false``. -- **checkFullUrl**: S'il faut ou non vérifier l'URL entière. Utile quand le - formulaire de connexion se trouve dans un sous-domaine différent. La valeur - par défaut est ``false``. - -Un vérificateur d'URL personnalisé peut par exemple être implémenté si on a -besoin de supporter des URLs spécifiques à un framework. Dans ce cas, -l'interface ``Authentication\UrlChecker\UrlCheckerInterface`` devrait être -implémentée. - -Pour plus de détails sur les vérificateurs d'URLs, -:doc:`reportez-vous à cette page `. - -Obtenir l'Authentificateur ou l'Identificateur qui a réussi -=========================================================== - -Après qu'un utilisateur a été identifié, vous voudrez sans doute inspecter -l'Authenticator qui a réussi à authentifier l'utilisateur, ou -interagir avec lui:: - - // Dans une action d'un contrôleur - $service = $this->request->getAttribute('authentication'); - - // Sera null en cas d'échec d'authentification, sinon un authentificateur. - $authenticator = $service->getAuthenticationProvider(); - -Vous pouvez tout aussi bien obtenir l'identificateur qui a identifié -l'utilisateur:: - - // Dans une action d'un contrôleur - $service = $this->request->getAttribute('authentication'); - - // Sera null en cas d'échec d'authentification, sinon un identificateur. - $identifier = $service->getIdentificationProvider(); - - -Utiliser conjointement des Authentificateurs Stateless et Stateful -================================================================== - -Quand vous utilisez ``HttpBasic``, ``HttpDigest`` avec d'autres -authentificateurs, vous devez vous souvenir que ces authentificateurs arrêteront -la requête si les identifiants de connexion sont absents ou invalides. C'est -indispensable puisque ces authentificateurs doivent envoyer dans la réponse des -en-têtes comportant un défi spécifique:: - - use Authentication\AuthenticationService; - - // Instancier le service - $service = new AuthenticationService(); - - // Charger les identificateurs - $service->loadIdentifier('Authentication.Password', [ - 'fields' => [ - 'username' => 'email', - 'password' => 'password' - ] - ]); - $service->loadIdentifier('Authentication.Token'); - - // Charger les authentificateurs en plaçant Basic en dernier. - $service->loadAuthenticator('Authentication.Session'); - $service->loadAuthenticator('Authentication.Form'); - $service->loadAuthenticator('Authentication.HttpBasic'); - -Si vous voulez combiner ``HttpBasic`` ou ``HttpDigest`` avec d'autres -authentificateurs, ayez conscience que ces authentificateurs interrompront la -requête et forceront l'ouverture d'une boîte de dialogue dans le navigateur. - -Gérer les Erreurs de Non-Authentification -========================================= - -Le composant ``AuthenticationComponent`` soulèvera une exception lorsque des -utilisateurs ne sont pas connectés. Vous pouvez convertir ces exceptions en -redirections en utilisant ``unauthenticatedRedirect`` dans la configuration de -l'\ ``AuthenticationService``. - -Vous pouvez aussi passer l'URI cible de la requête en cours en tant que -paramètre de requête en utilisant l'option ``queryParam``:: - - // Dans la méthode getAuthenticationService() de votre src/Application.php - - $service = new AuthenticationService(); - - // Configure la redirection en cas de non-authentification - $service->setConfig([ - 'unauthenticatedRedirect' => '/users/login', - 'queryParam' => 'redirect', - ]); - -Ensuite, dans la méthode login de votre contrôleur, vous pouvez utiliser -``getLoginRedirect()`` pour obtenir la cible de redirection en toute sécurité à -partir du paramètre de la *query string*:: - - public function login() - { - $result = $this->Authentication->getResult(); - - // Que l'on soit en POST ou GET, rediriger l'utilisateur s'il est connecté - if ($result->isValid()) { - // Utiliser le paramètre de redirection s'il est présent - $target = $this->Authentication->getLoginRedirect(); - if (!$target) { - $target = ['controller' => 'Pages', 'action' => 'display', 'home']; - } - return $this->redirect($target); - } - } - -Avoir Plusieurs Canaux d'Authentication -======================================= - -Dans une application qui fournit à la fois une API et une interface web, vous -voudrez probablement des configurations différentes d'authentification selon que -la requête est ou non une requête d'API. Par exemple, vous pourriez vouloir -utiliser une authentification JWT pour votre API, mais des sessions pour votre -interface web. Pour prendre en charge ces différents flux, vous pouvez renvoyer -des services d'authentification différents selon le chemin de l'URL, ou selon -n'importe quel autre attribut de la requête:: - - public function getAuthenticationService( - ServerRequestInterface $request - ): AuthenticationServiceInterface { - $service = new AuthenticationService(); - - // La configuration commune à l'API et au web est placée ici. - - if ($request->getParam('prefix') == 'Api') { - // Inclure les authentificateurs spécifiques pour l'API - } else { - // Authentificateurs spécifiques pour l'interface web. - } - - return $service; - } diff --git a/docs/fr/conf.py b/docs/fr/conf.py deleted file mode 100644 index b02032ef..00000000 --- a/docs/fr/conf.py +++ /dev/null @@ -1,9 +0,0 @@ -import sys, os - -# Append the top level directory of the docs, so we can import from the config dir. -sys.path.insert(0, os.path.abspath('..')) - -# Pull in all the configuration options defined in the global config file.. -from config.all import * - -language = 'fr' diff --git a/docs/fr/contents.rst b/docs/fr/contents.rst deleted file mode 100644 index 100881f6..00000000 --- a/docs/fr/contents.rst +++ /dev/null @@ -1,18 +0,0 @@ -Contents -######## - -.. toctree:: - :maxdepth: 2 - :caption: Authentification CakePHP - - /index - /authenticators - /identifiers - /password-hashers - /identity-object - /middleware - /authentication-component - /testing - /url-checkers - /view-helper - /migration-from-the-authcomponent diff --git a/docs/fr/identifiers.rst b/docs/fr/identifiers.rst deleted file mode 100644 index 2936398b..00000000 --- a/docs/fr/identifiers.rst +++ /dev/null @@ -1,202 +0,0 @@ -Identificateurs -############### - -Les identificateurs vont identifier un utilisateur ou un service à partir des -informations qui auront été extraites de la requête par les authentificateurs. -Les identificateurs peuvent prendre des options dans la méthode -``loadIdentifier``. -Voici un exemple général d'utilisation du *Password Identifier*:: - - $service->loadIdentifier('Authentication.Password', [ - 'fields' => [ - 'username' => 'email', - 'password' => 'passwd', - ], - 'resolver' => [ - 'className' => 'Authentication.Orm', - 'userModel' => 'Users', - 'finder' => 'active', - ], - 'passwordHasher' => [ - 'className' => 'Authentication.Fallback', - 'hashers' => [ - 'Authentication.Default', - [ - 'className' => 'Authentication.Legacy', - 'hashType' => 'md5', - ], - ] - ] - ]); - -Password -======== - -L'identificateur par mot de passe confronte les identifiants avec la source de -données. - -Options de configuration: - -- **fields**: Les champs à regarder. Par défaut - ``['username' => 'username', 'password' => 'password']``. Vous pouvez aussi - définir le ``username`` en tableau. Par exemple, utiliser - ``['username' => ['username', 'email'], 'password' => 'password']`` vous - permettra de confronter la valeur soit de la colonne ``username``, soit de la - colonne ``email``. -- **resolver**: Le résolveur d'identité. Par défaut ``Authentication.Orm``, qui - utilise l'ORM CakePHP. -- **passwordHasher**: Le hacheur de mots de passe. Par défaut - ``DefaultPasswordHasher::class``. - -Token -===== - -Confronte le jeton d'accès avec la source de données. - -Options de configuration: - -- **tokenField**: Le champ à confronter dans la base de données. Par défaut - ``token``. -- **dataField**: Le champ dans les données transmises par l'authentificateur. - Par défaut ``token``. -- **resolver**: Le résolveur d'identité. Par défaut ``Authentication.Orm``, qui - utilise l'ORM CakePHP. - -JWT Subject -=========== - -Confronte le jeton d'accès JWT avec la source de données. - -Options de configuration: - -- **tokenField**: Le champ à confronter dans la base de données. Par défaut - ``id``. -- **dataField**: La clé payload à partir de laquelle obtenir l'utilisateur. Par - défaut ``sub``. -- **resolver**: Le résolveur d'identité. Par défaut ``Authentication.Orm``, qui - utilise l'ORM CakePHP. - -LDAP -==== - -Confronte les identifiants fournis avec un serveur LDAP. Cet identificateur -nécessite l'extention PHP LDAP. - -Options de configuration: - -- **fields**: Les champs à regarder. Par défaut - ``['username' => 'username', 'password' => 'password']``. -- **host**: Le nom complet de domaine (FQDN) de votre serveur LDAP. -- **port**: Le port de votre serveur LDAP. Par défaut ``389``. -- **bindDN**: Le nom distinctif (*Distinguished Name*) de l'utilisateur - à identifier. Doit être *callable*. Les *binds* anonymes de - sont pas supportés. -- **ldap**: L'adaptateur d'extension. Par défaut - ``\Authentication\Identifier\Ldap\ExtensionAdapter``. Vous pouvez passer un - objet ou une classe personnalisée ici à condition qu'elle implémente - l'\ ``AdapterInterface``. -- **options**: Options supplémentaires LDAP, telles que - ``LDAP_OPT_PROTOCOL_VERSION`` ou ``LDAP_OPT_NETWORK_TIMEOUT``. Cf. - `php.net `__ - pour en savoir plus sur les options valides. - -Callback -======== - -Permet d'utiliser un callback pour l'identification. C'est utile pour des -identificateurs simples ou pour un prototypage rapide. - -Options de configuration: - -- **callback**: La valeur par défaut est ``null`` et entraînera une exception. - Vous devez impérativement placer un callback valide dans cette option pour - utiliser l'authentificateur. - -Les identificateurs Callback peuvent renvoyer soit ``null|ArrayAccess`` pour des -résultats simples, soit un ``Authentication\Authenticator\Result`` si vous -voulez transférer des messages d'erreur:: - - // Un identificateur simple par callback - $authenticationService->loadIdentifier('Authentication.Callback', [ - 'callback' => function($data) { - // faire la logique de l'identification - - // Renvoyer un tableau de l'utilisateur identifié - // ou null en cas d'échec - if ($result) { - return $result; - } - - return null; - }, - ]); - - // Utiliser un objet result pour renvoyer des messages d'erreur. - $authenticationService->loadIdentifier('Authentication.Callback', [ - 'callback' => function($data) { - // faire la logique de l'identification - - if ($result) { - return new Result($result, Result::SUCCESS); - } - - return new Result( - null, - Result::FAILURE_OTHER, - ['message' => 'Utilisateur effacé.'] - ); - }, - ]); - - -Résolveurs d'identité -===================== - -Les résolveurs d'identité fournissent des adaptateurs pour différentes sources -de données. Ils vous permettent de contrôler dans quelle source les identités -sont recherchées. Ils sont séparés des identificateurs, de sorte qu'ils sont -interchangeables indépendamment de la méthode d'identification (form, jwt, basic -auth). - -Résolveur ORM -------------- - -Le résolveur d'identité pour l'ORM CakePHP. - -Options de configuration: - -- **userModel**: Le modèle utilisateur dans lequel sont situées les identités. - Par défaut ``Users``. -- **finder**: Le finder à utiliser avec le modèle. Par défaut ``all``. - Pour en savoir plus sur les finders de modèle, consultez - `cette documentation `__. - -Afin d'utiliser le résolveur ORM, vous devez requérir ``cakephp/orm`` dans votre -fichier ``composer.json`` (si vous n'utilisez pas déjà le framework CakePHP -complet). - -Écrire vos propres résolveurs ------------------------------ - -Chaque ORM ou source de données peut être adapté pour fonctionner avec -l'authentification en créant un résolveur. Les résolveurs doivent implémenter -``Authentication\Identifier\Resolver\ResolverInterface`` et devraient être -placés dans le namespace ``App\Identifier\Resolver``. - -Les résolveurs peuvent être configurés en utilisant l'option de configuration -``resolver``:: - - $service->loadIdentifier('Authentication.Password', [ - 'resolver' => [ - // peut être un nom de classe complet: \Some\Other\Custom\Resolver::class - 'className' => 'MyResolver', - // Passer des options supplémentaires pour le constructeur du résolveur. - 'option' => 'value', - ], - ]); - -Ou être injectés avec un setter:: - - $resolver = new \App\Identifier\Resolver\CustomResolver(); - $identifier = $service->loadIdentifier('Authentication.Password'); - $identifier->setResolver($resolver); diff --git a/docs/fr/identity-object.rst b/docs/fr/identity-object.rst deleted file mode 100644 index f3b8ff11..00000000 --- a/docs/fr/identity-object.rst +++ /dev/null @@ -1,120 +0,0 @@ -Objets Identité -############### - -Les objets Identité sont renvoyés par le service d'authentification et rendus -disponibles dans la requête. Les identités fournissent une méthode -``getIdentifier()`` qui peut être appelée pour obtenir dans l'identité la valeur -de l'identifiant primaire de la connexion en cours. - -La raison pour laquelle cet objet existe est le besoin de fournir une interface -qui réalise ces implémentations/sources:: - - // Service - $authenticationService - ->getIdentity() - ->getIdentifier() - - // Component - $this->Authentication - ->getIdentity() - ->getIdentifier(); - - // Request - $this->request - ->getAttribute('identity') - ->getIdentifier(); - -L'objet identité fournit ArrayAccess mais aussi une méthode ``get()`` pour -accéder aux données. Il est fortement recommandé d'utiliser la méthode ``get()`` -plutôt qu'un accès à la façon des tableaux parce que la méthode ``get()`` a -connaissance du mappage des champs:: - - $identity->get('email'); - $identity->get('username'); - -La méthode ``get()`` peut aussi être typée explicitement *via* un -méta-fichier EDI, par exemple avec -`IdeHelper `__. - -Si vous voulez, vous pouvez malgré tout utiliser l'accès par propriétés:: - - $identity->email; - $identity->username; - -La classe par défaut de l'objet identité peut être configurée pour mapper des -champs. C'est particulièrement utile si l'identifiant de l'identité n'est pas un -champ conventionnel ``id`` ou si vous voulez mapper des champs avec des noms -plus généraux ou plus communs:: - - $identity = new Identity($data, [ - 'fieldMap' => [ - 'id' => 'uid', - 'username' => 'prenom' - ] - ]); - -Créer votre propre Objet Identité ---------------------------------- - -Par défaut le plugin Authentication va envelopper les données utilisateur -renvoyées dans un ``IdentityDecorator`` qui mandate (*proxy*) l'accès aux -méthodes et aux propriétés. Si vous voulez créer votre propre objet identité, -votre objet doit implémenter ``IdentityInterface``. - -Implémenter IdentityInterface dans votre classe User ----------------------------------------------------- - -Si vous voulez continuer à utiliser votre classe User existante avec ce plugin, -vous pouvez implémenter l'interface ``Authentication\IdentityInterface``:: - - namespace App\Model\Entity; - - use Authentication\IdentityInterface; - use Cake\ORM\Entity; - - class User extends Entity implements IdentityInterface - { - /** - * Authentication\IdentityInterface method - */ - public function getIdentifier() - { - return $this->id; - } - - /** - * Authentication\IdentityInterface method - */ - public function getOriginalData() - { - return $this; - } - - // Autres méthodes - } - -Utiliser un Décorateur d'Identité Personnalisé ----------------------------------------------- - -Si les objets résultant de vos identificateurs ne peuvent pas être modifiés pour -implémenter l'interface ``IdentityInterface``, vous pouvez implémenter un -décorateur personnalisé qui l'implémente lui-même:: - - // Vous pouvez utiliser un callable... - $identityResolver = function ($data) { - return new MyCustomIdentity($data); - }; - - //...ou un nom de classe pour définir le wrapper d'identité. - $identityResolver = MyCustomIdentity::class; - - // Ensuite passez-le dans la configuration du service - $service = new AuthenticationService([ - 'identityClass' => $identityResolver, - 'identifiers' => [ - 'Authentication.Password' - ], - 'authenticators' => [ - 'Authentication.Form' - ] - ]); diff --git a/docs/fr/index.rst b/docs/fr/index.rst deleted file mode 100644 index e216f554..00000000 --- a/docs/fr/index.rst +++ /dev/null @@ -1,264 +0,0 @@ -Prise en main rapide -#################### - -Installez le plugin avec `composer `_ depuis le -répertoire ROOT de votre projet CakePHP (là où se trouve le fichier -**composer.json**). - -.. code-block:: bash - - php composer.phar require cakephp/authentication - -La version 4 du Plugin Authentication est compatible avec CakePHP 5. - -Chargez le plugin en ajoutant l'instruction suivante dans le fichier -``src/Application.php`` de votre projet:: - - public function bootstrap(): void - { - parent::bootstrap(); - - $this->addPlugin('Authentication'); - } - - -Pour commencer -============== - -Le plugin d'authentification s'intègre dans votre application comme un -`middleware `_. Il -peut aussi être utilisé comme un composant pour faciliter l'accès sans -authentification. Tout d'abord, mettons en place le middleware. Dans votre -**src/Application.php**, ajoutez ce qui suit aux imports de la classe:: - - use Authentication\AuthenticationService; - use Authentication\AuthenticationServiceInterface; - use Authentication\AuthenticationServiceProviderInterface; - use Authentication\Identifier\AbstractIdentifier; - use Authentication\Middleware\AuthenticationMiddleware; - use Cake\Http\MiddlewareQueue; - use Cake\Routing\Router; - use Psr\Http\Message\ServerRequestInterface; - - -Ensuite, ajoutez ``AuthenticationServiceProviderInterface`` aux interfaces implémentées -par votre application:: - - class Application extends BaseApplication implements AuthenticationServiceProviderInterface - - -Puis modifier votre méthode ``middleware()`` pour la faire ressembler à ceci:: - - public function middleware(MiddlewareQueue $middlewareQueue): MiddlewareQueue - { - $middlewareQueue->add(new ErrorHandlerMiddleware(Configure::read('Error'))) - // Autres middleware fournis par CakePHP. - ->add(new AssetMiddleware()) - ->add(new RoutingMiddleware($this)) - ->add(new BodyParserMiddleware()) - - // Ajoutez le AuthenticationMiddleware. Il doit se trouver - // après routing et body parser. - ->add(new AuthenticationMiddleware($this)); - - return $middlewareQueue(); - } - -.. warning:: - L'ordre des middlewares est important. Assurez-vous d'avoir - ``AuthenticationMiddleware`` après les middlewares routing et body parser. - Si vous avez des problèmes pour vous connecter avec des requêtes JSON ou si - les redirections sont incorrectes, revérifiez l'ordre de vos middlewares. - -``AuthenticationMiddleware`` appellera une méthode-crochet (*hook*) dans votre -application quand il commencera à traiter la requête. Cette méthode-crochet -permet à votre application de définir l'\ ``AuthenticationService`` qu'elle veut -utiliser. Ajoutez la méthode suivante à votre **src/Application.php**:: - - /** - * Renvoie une instance du fournisseur de service. - * - * @param \Psr\Http\Message\ServerRequestInterface $request Request - * @return \Authentication\AuthenticationServiceInterface - */ - public function getAuthenticationService(ServerRequestInterface $request): AuthenticationServiceInterface - { - $service = new AuthenticationService(); - - // Définissez vers où les utilisateurs doivent être redirigés s'ils ne - // sont pas authentifiés - $service->setConfig([ - 'unauthenticatedRedirect' => Router::url([ - 'prefix' => false, - 'plugin' => null, - 'controller' => 'Users', - 'action' => 'login', - ]), - 'queryParam' => 'redirect', - ]); - - $fields = [ - AbstractIdentifier::CREDENTIAL_USERNAME => 'email', - AbstractIdentifier::CREDENTIAL_PASSWORD => 'password' - ]; - // Chargez les authentificateurs. Session est censé figurer en premier. - $service->loadAuthenticator('Authentication.Session'); - $service->loadAuthenticator('Authentication.Form', [ - 'fields' => $fields, - 'loginUrl' => Router::url([ - 'prefix' => false, - 'plugin' => null, - 'controller' => 'Users', - 'action' => 'login', - ]), - ]); - - // Chargez les identificateurs - $service->loadIdentifier('Authentication.Password', compact('fields')); - - return $service; - } - -Premièrement, nous configurons ce qu'il faut faire lorsque les utilisateurs ne -sont pas authentifiés. -Puis nous rattachons les :doc:`/authenticators` ``Session`` et ``Form`` qui -définissent les mécanismes que votre application utilisera pour authentifier les -utilisateurs. ``Session`` active l'identification des utilisateurs à partir des -données de session, tandis que ``Form`` active le traitement par un formulaire -de connexion à l'adresse ``loginUrl``. -Enfin, nous rattachons un :doc:`identifier ` pour convertir les -identifiants que l'utilisateur nous donnera en une -:doc:`identity ` qui représentera l'utilisateur connecté. - -Si l'un des authentificateurs configurés a été en mesure de valider les -identifiants utilisateur, le middleware ajoutera le service d'authentification à -l'objet requête en tant qu'\ `attribut `_. - -Ensuite, chargez le :doc:`/authentication-component` dans votre -``AppController``:: - - // dans src/Controller/AppController.php - public function initialize() - { - parent::initialize(); - - $this->loadComponent('Authentication.Authentication'); - } - -Par défaut, ce composant exigera un utilisateur authentifié pour **toutes** les -actions. Vous pouvez désactiver ce comportement dans certains contrôleurs en -utilisant ``allowUnauthenticated()``:: - - // dans beforeFilter ou initialize d'un contrôleur - // Faire que view et index n'exigent pas un utilisateur connecté. - $this->Authentication->allowUnauthenticated(['view', 'index']); - -Construire une Action Login -=========================== - -Une fois que vous aurez appliqué le middleware à votre application, vous aurez -besoin d'un moyen pour connecter les utilisateurs. Tout d'abord, générez un -modèle et un contrôleur Users avec bake: - -.. code-block:: shell - - bin/cake bake model Users - bin/cake bake controller Users - -Ensuite, nous allons ajouter une action de connexion basique à votre -``UsersController``. Cela devrait ressembler à:: - - // dans src/Controller/UsersController.php - public function login() - { - $result = $this->Authentication->getResult(); - // Si l'utilisateur est connecté, le renvoyer ailleurs - if ($result && $result->isValid()) { - $target = $this->Authentication->getLoginRedirect() ?? '/home'; - return $this->redirect($target); - } - if ($this->request->is('post')) { - $this->Flash->error('Identifiant ou mot de passe invalide'); - } - } - -Assurez-vous d'autoriser l'accès à l'action ``login`` dans le callback -``beforeFilter()`` de votre contrôleur comme mentionné dans la section -précédente, de façon à ce que les utilisateurs non authentifiés puissent y avoir -accès:: - - // dans src/Controller/UsersController.php - public function beforeFilter(\Cake\Event\EventInterface $event) - { - parent::beforeFilter($event); - - $this->Authentication->allowUnauthenticated(['login']); - } - -Ensuite nous allons ajouter un template de vue pour notre formulaire de -connexion:: - - // dans templates/Users/login.php -
- Form->create() ?> -
- - Form->control('email') ?> - Form->control('password') ?> -
- Form->button(__('Login')); ?> - Form->end() ?> -
- -Puis ajoutez une action de déconnexion toute simple:: - - // dans src/Controller/UsersController.php - public function logout() - { - $this->Authentication->logout(); - return $this->redirect(['controller' => 'Users', 'action' => 'login']); - } - -Nous n'avons pas besoin de template pour notre action logout puisque nous -faisons une redirection à la fin de celle-ci. - -Ajouter un Hachage de Mot de Passe -================================== - -Pour connecter vos utilisateurs, vous aurez besoin d'avoir des mots de passe -hachés. Vous pouvez hacher des mots de passe automatiquement quand les -utilisateurs mettent à jour leur mot de passe en utilisant un setter de -l'entité:: - - // dans src/Model/Entity/User.php - use Authentication\PasswordHasher\DefaultPasswordHasher; - - class User extends Entity - { - // ... autres méthodes - - // Hacher automatiquement les mots de passe quand ils sont modifiés. - protected function _setPassword(string $password) - { - $hasher = new DefaultPasswordHasher(); - return $hasher->hash($password); - } - } - -Vous devriez maintenant pouvoir aller à ``/users/add`` et enregistrer un nouvel -utilisateur. Une fois enregistré, vous pouvez aller à ``/users/login`` et vous -connecter sous le nom de l'utilisateur que vous venez de créer. - - -Pour en savoir plus -=================== - -* :doc:`/authenticators` -* :doc:`/identifiers` -* :doc:`/password-hashers` -* :doc:`/identity-object` -* :doc:`/authentication-component` -* :doc:`/migration-from-the-authcomponent` -* :doc:`/url-checkers` -* :doc:`/testing` -* :doc:`/view-helper` diff --git a/docs/fr/middleware.rst b/docs/fr/middleware.rst deleted file mode 100644 index 1707e81d..00000000 --- a/docs/fr/middleware.rst +++ /dev/null @@ -1,69 +0,0 @@ -Middleware -########## - -``AuthenticationMiddleware`` forme le cœur du plugin authentication. Il -intercepte chaque requête à destination de votre application et tente -d'authentifier l'utilisateur avec l'un des authentificateurs. Chaque -authentificateur est essayé dans l'ordre, jusqu'à ce qu'un utilisateur soit -authentifié ou qu'aucun utilisateur ne soit trouvé. Les attributs -``authentication``, ``identity`` et ``authenticationResult`` sont définis sur la -requête et contiennent l'identité, s'il y en a une qui a été trouvée, et l'objet -résultat de l'authentification qui peut contenir des messages d'erreur -supplémentaires fournis par les authentificateurs. - -À la fin de chaque requête, l'\ ``identity`` est rendue persistante dans chaque -authentificateur qui supporte les états, tels que l'authentificateur -``Session``. - -Configuration -============= - -Toute la configuration du middleware se fait dans l'\ ``AuthenticationService``. -Vous pouvez utiliser les options de configuration suivantes sur le service: - -- ``identityClass`` - Le nom de la classe de l'identité ou un constructeur - d'identité callable. -- ``identityAttribute`` - L'attribut de la requête utilisé pour stocker - l'identité. Par défaut ``identity``. -- ``unauthenticatedRedirect`` - L'URL vers laquelle rediriger les erreurs dues à - l'absence d'authentification. -- ``queryParam`` - Le nom du paramètre de la query string qui contiendra l'URL - précédemment bloquée en cas de redirection due à l'absence d'authentification, - ou null pour désactiver l'ajout de l'URL refusée. Par défaut ``null``. - - -Configurer Plusieurs Paramétrages d'Authentification -==================================================== - -Si votre application a besoin de plusieurs paramétrages d'authentification -différents pour différentes parties de l'application, par exemple l'API et le -Web UI, vous pouvez y parvenir en utilisant une logique conditionnelle dans la -méthode crochet ``getAuthenticationService()`` de vos applications. En inspectant -l'objet requête, vous pouvez configurer l'authentification de façon appropriée:: - - public function getAuthenticationService(ServerRequestInterface $request): AuthenticationServiceInterface - { - $path = $request->getPath(); - - $service = new AuthenticationService(); - if (strpos($path, '/api') === 0) { - // Accepter uniquement les jetons d'accès API - $service->loadAuthenticator('Authentication.Token'); - $service->loadIdentifier('Authentication.Token'); - - return $service; - } - - // Authentication web - // Supporter les sessions et le formulaire de connexion. - $service->loadAuthenticator('Authentication.Session'); - $service->loadAuthenticator('Authentication.Form'); - - $service->loadIdentifier('Authentication.Password'); - - return $service; - } - -De même que l'exemple ci-dessus utilise un préfixe de chemin, vous pouvez -appliquer une logique similaire au sous-domaine, au domaine, ou à n'importe quel -autre en-tête ou attribut présent dans la requête. diff --git a/docs/fr/migration-from-the-authcomponent.rst b/docs/fr/migration-from-the-authcomponent.rst deleted file mode 100644 index 013ef7b7..00000000 --- a/docs/fr/migration-from-the-authcomponent.rst +++ /dev/null @@ -1,324 +0,0 @@ -Migration depuis AuthComponent -############################## - -Différences -=========== - -- Volontairement, ce plugin **ne** gère **pas** les autorisations. La - fonctionnalité a été `découplée - `__ de l'autorisation - dans le but de proposer une - `séparation des préoccupations `__ - claire. Cf. aussi - `Contrôle d'accès `__. - Ce plugin gère seulement l'\ *identification* et l'\ *authentification*. On - peut avoir un autre plugin pour l'autorisation. -- Il n'y a pas de vérification automatique de la session. Pour aller chercher - les informations utilisateur dans la session, vous devrez utiliser le - ``SessionAuthenticator``. Il va vérifier dans la session s'il y a des données - sous la clé de session configurée, et les place ensuite dans l'objet - Identité. -- Les informations sur l'utilisateur ne sont plus disponibles en passant par - l'ancien AuthComponent, mais sont accessibles *via* un attribut de la requête - et encapsulées dans un objet Identité: - ``$request->getAttribute('authentication')->getIdentity();``. - En complément, vous pouvez exploiter les méthodes ``getIdentity()`` ou - ``getIdentityData()`` de ``AuthenticationComponent``. -- La logique du processus d'authentification a été scindée en authentificateurs - et identificateurs. Un authentificateur va extraire les identifiants de - l'utilisateur (*credentials*) dans la requête, tandis que les - identificateurs les vérifieront et désigneront l'utilisateur correspondant. -- DigestAuthenticate a été renommé en HttpDigestAuthenticator. -- BasicAuthenticate a été renommé en HttpBasicAuthenticator. - -Similitudes -=========== - -- Tous les adaptateurs d'authentification existants, Form, Basic, Digest sont - toujours là mais ont été remodelés en authentificateurs. - -Identificateurs et authentificateurs -==================================== - -Suivant en cela le principe de séparation des préoccupations, les anciens objets -d'authentification ont été scindés en objets bien séparés, les identificateurs -et les authentificateurs. - -- Les **authentificateurs** prennent la requête entrante et tentent d'en - extraire les identifiants de l'utilisateur. S'ils les trouvent, ils les - passent à une collection d'identificateurs qui recherchent où se trouve - l'utilisateur. - Pour cette raison, les authentificateurs prennent une IdentifierCollection en - premier argument dans leur constructeur. -- Les **identificateurs** confrontent les identifiants à un système de stockage - (par exemple des tables ORM, LDAP, etc) et renvoient les informations de - l'utilisateur identifié. - -Cela facilite le changement de logique d'identification en tant que de besoin, -ou l'utilisation de plusieurs sources d'informations sur les utilisateurs. - -Si vous voulez implémenter vos propres identificateurs, votre identificateur -doit implémenter l'interface ``IdentifierInterface``. - -Migrer votre système d'authentification -======================================= - -La première chose à faire pour migrer votre application est de charger le plugin -authentication dans la méthode bootstrap de votre application:: - - public function bootstrap(): void - { - parent::bootstrap(); - $this->addPlugin('Authentication'); - } - -Ensuite, modifiez votre application pour lui faire implémenter l'interface de -fournisseur de service d'authentification. Cela permet à votre -AuthenticationMiddleware de savoir comment obtenir un service d'authentification -à partir de votre application:: - - // dans src/Application.php - - // Ajoutez les instructions 'use' suivantes. - use Authentication\AuthenticationService; - use Authentication\AuthenticationServiceInterface; - use Authentication\AuthenticationServiceProviderInterface; - use Authentication\Middleware\AuthenticationMiddleware; - use Psr\Http\Message\ResponseInterface; - use Psr\Http\Message\ServerRequestInterface; - - // Ajoutez l'interface d'authentification. - class Application extends BaseApplication implements AuthenticationServiceProviderInterface - { - /** - * Renvoie une instance du service provider. - * - * @param \Psr\Http\Message\ServerRequestInterface $request Request - * @param \Psr\Http\Message\ResponseInterface $response Response - * @return \Authentication\AuthenticationServiceInterface - */ - public function getAuthenticationService(ServerRequestInterface $request) : AuthenticationServiceInterface - { - $service = new AuthenticationService(); - // Configurez le service. (cf. ci-dessous pour les détails) - return $service; - } - } - -Puis ajoutez l'\ ``AuthenticationMiddleware`` à votre application:: - - // dans src/Application.php - public function middleware($middlewareQueue) - { - // Divers autres middlewares pour la gestion des erreurs, le routing, etc, sont ajoutés ici. - - // Ajoutez le middleware à la middleware queue - $middlewareQueue->add(new AuthenticationMiddleware($this)); - - return $middlewareQueue; - } - -Migrer vos réglages de AuthComponent ------------------------------------- - -Le tableau de configuration de ``AuthComponent`` a besoin d'être scindé en -identificateurs et authentificateurs lors de la configuration du service. Ainsi, -si votre ``AuthComponent`` était configuré de cette façon:: - - $this->loadComponent('Auth', [ - 'authentication' => [ - 'Form' => [ - 'fields' => [ - 'username' => 'email', - 'password' => 'password', - ] - ] - ] - ]); - -Vous devrez maintenant le configurer de cette façon:: - - // Instancier le service - $service = new AuthenticationService(); - - // Charger les identificateurs - $service->loadIdentifier('Authentication.Password', [ - 'fields' => [ - 'username' => 'email', - 'password' => 'password', - ] - ]); - - // Charger les authentificateurs - $service->loadAuthenticator('Authentication.Session'); - $service->loadAuthenticator('Authentication.Form'); - -Si vous aviez personnalisé le ``userModel``, vous pouvez utiliser la -configuration suivante:: - - // Instancier le service - $service = new AuthenticationService(); - - // Charger les identificateurs - $service->loadIdentifier('Authentication.Password', [ - 'resolver' => [ - 'className' => 'Authentication.Orm', - 'userModel' => 'Employes', - ], - 'fields' => [ - 'username' => 'email', - 'password' => 'password', - ] - ]); - -Bien qu'il y ait un petit peu plus de code qu'avant, vous avez plus de souplesse -dans la gestion des authentifications. - -Action Login ------------- - -L'\ ``AuthenticationMiddleware`` va se charger de la vérification et de la -définition de l'identité de l'utilisateur en s'appuyant sur les -authentificateurs. D'habitude, après la connexion, ``AuthComponent`` redirigeait -vers une URL définie dans la configuration. Pour rediriger après une connexion -réussie, changez votre action login pour vérifier le résultat de la nouvelle -identité:: - - public function login() - { - $result = $this->Authentication->getResult(); - - // Que l'on soit en POST ou GET, rediriger l'utilisateur s'il est connecté - if ($result->isValid()) { - $target = $this->Authentication->getLoginRedirect(); - return $this->redirect($target); - } - - // Afficher une erreur si l'utilisateur a validé le formulaire et que - // l'authentification a échoué - if ($this->request->is(['post'])) { - $this->Flash->error('Identifiant ou mot de passe invalide'); - } - } - -Vérifier les identités ----------------------- - -Après avoir appliqué le middleware vous pouvez utiliser les données d'identité -en consultant l'attribut ``identity`` de la requête. Cela remplace les appels à -``$this->Auth->user()`` que vous utilisiez jusqu'à présent. Si l'utilisateur en -cours n'est pas authentifié ou si les identifiants fournis étaient invalides, -l'attribut ``identity`` sera ``null``:: - - $user = $request->getAttribute('identity'); - -Pour plus de détails sur le résultat du processus d'authentification, vous -pouvez accéder à l'objet Résultat qui est aussi fourni dans la requête et est -accessible sous l'attribut ``authentication``:: - - $result = $request->getAttribute('authentication')->getResult(); - // Booléen si le résultat est valide - $isValid = $result->isValid(); - // Un code de statut - $statusCode = $result->getStatus(); - // Un tableau de messages d'erreur, ou des données si l'identificateur en a fournies - $errors = $result->getErrors(); - -À chaque endroit où vous appeliez ``AuthComponent::setUser()``, vous devriez à -présent utiliser ``setIdentity()``:: - - // Supposons que vous ayez besoin de rechercher un utilisateur à partir d'un jeton d'accès - $user = $this->Users->find('byToken', ['token' => $token])->first(); - - // Rendre l'utilisateur persistant dans les authentificateurs configurés. - $this->Authentication->setIdentity($user); - - -Migrer la logique allow/deny ----------------------------- - -Comme ``AuthComponent``, l'\ ``AuthenticationComponent`` rend aisé le marquage -d'actions spécifiques comme étant 'publiques' et ne nécessitant pas la présence -d'une identité valide:: - - // Dans la méthode beforeFilter de votre contrôleur. - $this->Authentication->allowUnauthenticated(['view']); - -Chaque appel à ``allowUnauthenticated()`` écrasera la liste d'actions en cours. - -Migrer les Redirections en cas de Non Authentification -====================================================== - -Par défaut, ``AuthComponent`` renvoie les utilisateurs vers la page de connexion -lorsqu'une authentification est exigée. Au contraire, dans ce scénario, -l'\ ``AuthenticationComponent`` de ce plugin soulèvera une exception. Vous -pouvez convertir cette exception en redirection en utilisant -``unauthenticatedRedirect`` dans la configuration de -l'\ ``AuthenticationService``. - -Vous pouvez aussi passer l'URI ciblée par la requête en cours en tant que -paramètre dans la query string de la redirection avec l'option ``queryParam``. -Notez que le paramètre de redirection n'est ajouté que pour les requêtes GET afin -d'éviter de rediriger vers des actions non-GET après la connexion:: - - // Dans la méthode getAuthenticationService() de votre src/Application.php - - $service = new AuthenticationService(); - - // Configurer la redirection en cas de non authentification - $service->setConfig([ - 'unauthenticatedRedirect' => '/users/login', - 'queryParam' => 'redirect', - ]); - -Puis, dans la méthode login de votre contrôleur, vous pouvez utiliser en toute -sécurité ``getLoginRedirect()`` pour obtenir la cible redirigée, à partir du -paramètre de la query string:: - - public function login() - { - $result = $this->Authentication->getResult(); - - // Que l'on soit en POST ou GET, rediriger l'utilisateur s'il est connecté - if ($result->isValid()) { - // Utiliser le paramètre de redirection s'il est présent. - $target = $this->Authentication->getLoginRedirect(); - if (!$target) { - $target = ['controller' => 'Pages', 'action' => 'display', 'home']; - } - return $this->redirect($target); - } - } - -Migrer la Mise à Niveau de la Logique de Hachage -================================================ - -Si votre application utilise la fonctionnalité de ``AuthComponent`` de mise à -niveau du hachage. Vous pouvez répliquer cette logique dans ce plugin en tirant -parti de l'\ ``AuthenticationService``:: - - public function login() - { - $result = $this->Authentication->getResult(); - - // Que l'on soit en POST ou GET, rediriger l'utilisateur s'il est connecté - if ($result->isValid()) { - $authService = $this->Authentication->getAuthenticationService(); - - // Obtenir l'identificateur qui a été utilisé pour l'authentification. - $identifier = $authService->getIdentificationProvider(); - if ($identifier !== null && $identifier->needsPasswordRehash()) { - // Le re-hachage se produit lors de la sauvegarde. - $user = $this->Users->get($this->Authentication->getIdentityData('id')); - $user->password = $this->request->getData('password'); - $this->Users->save($user); - } - - // Rediriger vers une page connectée - return $this->redirect([ - 'controller' => 'Pages', - 'action' => 'display', - 'home' - ]); - } - } diff --git a/docs/fr/password-hashers.rst b/docs/fr/password-hashers.rst deleted file mode 100644 index 29b2e2b3..00000000 --- a/docs/fr/password-hashers.rst +++ /dev/null @@ -1,80 +0,0 @@ -Hacheurs de mots de passe -######################### - -Default -======= - -Ce hacheur utilise la constante PHP ``PASSWORD_DEFAULT`` comme méthode de -cryptage. Le type de hachage par défaut est ``bcrypt``. - -Cf. `la documentation PHP `__ -pour plus d'informations sur bcrypt et le hachage de mots de passe de PHP. - -Les options de configuration pour cet adaptateur sont: - -- **hashType**: L'algorithme de hachage à utiliser. Les valeurs valides sont - celles qui sont supportées par l'argument ``$algo`` de ``password_hash()``. - Par défaut ``PASSWORD_DEFAULT``. -- **hashOptions**: Tableau associatif d'options. Consultez le manuel PHP pour - les options supportées pour chaque type de hachage. Par défaut un tableau - vide. - -Legacy -====== - -C'est un hacheur de mots de passe pour les applications migrées de CakePHP2. - -Fallback -======== - -Le hacheur de mots de passe de repli (*fallback*) vous permet de -configurer plusieurs hacheurs qu'il vérifiera séquentiellement. Cela permet aux -utilisateurs de se connecter avec un ancien type de hachage jusqu'à ce que leur -mot de passe soit redéfini et mis à niveau vers le nouveau hachage. - -Mettre à Niveau les Algorithmes de Hachage -========================================== - -CakePHP propose un moyen propre de migrer les mots de passe de vos utilisateurs -d'un algorithme à un autre ; cela s'accomplit avec la classe -``FallbackPasswordHasher``. En supposant que vous veuillez migrer un mot de -passe Legacy vers le hacheur par défaut bcrypt, vous pouvez configurer le -hacheur fallback comme suit:: - - $service->loadIdentifier('Authentication.Password', [ - // Autres options de configuration - 'passwordHasher' => [ - 'className' => 'Authentication.Fallback', - 'hashers' => [ - 'Authentication.Default', - [ - 'className' => 'Authentication.Legacy', - 'hashType' => 'md5', - 'salt' => false // coupe l'utilisation par défaut du sel - ], - ] - ] - ]); - -Ensuite, dans votre action login, vous pouvez utiliser le service -d'authentification pour accéder à l'identificateur ``Password`` et vérifier si -le mot de passe de l'utilisateur actuel a besoin d'être mis à niveau:: - - public function login() - { - $authentication = $this->request->getAttribute('authentication'); - $result = $authentication->getResult(); - - // Que l'on soit en POST ou GET, rediriger l'utilisateur s'il est connecté - if ($result->isValid()) { - // En supposant que vous utilisez l'identificateur `Password`. - if ($authentication->identifiers()->get('Password')->needsPasswordRehash()) { - // Le re-hachage se produit lors de la sauvegarde. - $user = $this->Users->get($authentication->getIdentity()->getIdentifier()); - $user->password = $this->request->getData('password'); - $this->Users->save($user); - } - - // Rediriger ou afficher un template. - } - } diff --git a/docs/fr/testing.rst b/docs/fr/testing.rst deleted file mode 100644 index 508875bd..00000000 --- a/docs/fr/testing.rst +++ /dev/null @@ -1,64 +0,0 @@ -Tester avec Authentication -########################## - -Une fois le middleware ``authentication`` activé dans votre application, vous -aurez besoin de simuler des identifiants de connexion dans vos tests -d'intégration. -Pour commencer, assurez-vous que vos tests de controller ou de middleware -utilisent ``IntegrationTestTrait``:: - - // Dans un test de controller. - use Cake\TestSuite\IntegrationTestTrait; - use Cake\TestSuite\TestCase; - - class ArticlesControllerTest extends TestCase - { - use IntegrationTestTrait; - - // Méthodes de test et helpers - } - -Selon le type d'authentification que vous utilisez, vous aurez besoin de -simuler les identifiants de connexion d'une certaine façon ou d'une autre. -Examinons quelques types d'authentification parmi les plus répandus. - -Authentification par la session -=============================== - -L'authentification par la session implique de simuler les informations -utilisateur qui devraient normalement se trouver dans la session. Dans vos -scénarios de test vous pouvez définir une méthode helper qui vous 'connecte':: - - protected function login($userId = 1) - { - $users = TableRegistry::getTableLocator()->get('Users'); - $user = $users->get($userId); - $this->session(['Auth' => $user]); - } - -Dans vos tests d'intégration vous pouvez utiliser ``login()`` pour simuler un -utilisateur connecté:: - - public function testGet() - { - $this->login(); - $this->get('/bookmarks/1'); - $this->assertResponseOk(); - } - -Authentification par jeton d'accès -================================== - -Avec l'authentification par jeton d'accès, vous aurez besoin de simuler -l'en-tête ``Authorization``. Après avoir obtenu un jeton d'accès valide, -paramétrez la requête:: - - public function testGet() - { - $token = $this->getToken(); - $this->configRequest([ - 'headers' => ['Authorization' => 'Bearer ' . $token] - ]); - $this->get('/api/bookmarks'); - $this->assertResponseOk(); - } diff --git a/docs/fr/url-checkers.rst b/docs/fr/url-checkers.rst deleted file mode 100644 index 517aa35c..00000000 --- a/docs/fr/url-checkers.rst +++ /dev/null @@ -1,40 +0,0 @@ -Vérificateurs d'URL -################### - -Afin de fournir une solution abstraite et ignorante du framework, des -vérificateurs d'URL ont été implémentés qui vous permettent de personnaliser si -besoin la comparaison de l'URL en cours, par exemple avec le routage d'un autre -framework. - -Vérificateurs inclus -==================== - -DefaultUrlChecker ------------------ - -Le vérificateur par défaut vous permet de comparer une URL par expression -régulière ou chaînes URL. - -Options: - -- **checkFullUrl**: Pour comparer l'URL entière, y compris le protocole, l'hôte - et le port, ou pas. La valeur par défaut est ``false`` -- **useRegex**: Compare l'URL par une expression régulière fournie dans - l'argument ``$loginUrls`` du vérificateur. - -CakeRouterUrlChecker --------------------- - -Options: - -Utilisez ce vérificateur si vous voulez utiliser la notation en tableaux du -système de routage de CakePHP. Le vérificateur marche aussi avec les routes -nommées (*named routes*). - -- **checkFullUrl**: Pour comparer l'URL entière, y compris le protocole, l'hôte - et le port, ou pas. La valeur par défaut est ``false`` - -Implémenter votre propre Vérificateur -------------------------------------- - -Un vérificateur d'URL **doit** implémenter l'interface ``UrlCheckerInterface``. diff --git a/docs/fr/view-helper.rst b/docs/fr/view-helper.rst deleted file mode 100644 index 25d9f625..00000000 --- a/docs/fr/view-helper.rst +++ /dev/null @@ -1,28 +0,0 @@ -View Helper (Assistant) -======================= - -Dans votre AppView, chargez le Helper ainsi:: - - $this->loadHelper('Authentication.Identity'); - -Pour vérifier très simplement si l'utilisateur est connecté, vous pouvez -utiliser:: - - if ($this->Identity->isLoggedIn()) { - ... - } - -Il est possible d'obtenir les informations sur l'utilisateur avec:: - - $username = $this->Identity->get('username'); - -Vous pouvez utiliser la vérification suivante pour savoir si un enregistrement -qui appartient à un certain utilisateur est bien celui de l'utilisateur -actuellement connecté, et même pour comparer d'autres champs:: - - $isCurrentUser = $this->Identity->is($user->id); - $isCurrentRole = $this->Identity->is($user->role_id, 'role_id'); - -Cette méthode est surtout une méthode de confort pour les cas simples et n'a pas -vocation à remplacer une quelconque implémentation d'autorisations à proprement -parler. diff --git a/docs/ja/authentication-component.rst b/docs/ja/authentication-component.rst deleted file mode 100644 index 6c5be60e..00000000 --- a/docs/ja/authentication-component.rst +++ /dev/null @@ -1,103 +0,0 @@ -認証 Component -=================== - -認証結果にアクセスするために ``AuthenticationComponent`` にアクセスすることができます。 -ユーザーの身元とログアウトユーザーを取得できます。 -他のコンポーネントと同じように ``AppController::initialize()`` でコンポーネントをロードします :: - - $this->loadComponent('Authentication.Authentication', [ - 'logoutRedirect' => '/users/login' // デフォルトはfalse - ]); - -一旦ロードされると、全てのアクションが認証済みユーザーでしか入れなくなります。 -しかし、他のアクセス制御チェックは行わないでください。 -このチェックを無効にするには ``allowUnauthenticated()`` を使います:: - - // beforeFilter メソッドの中に記述してください。 - $this->Authentication->allowUnauthenticated(['view']); - -ログインしているユーザーへのアクセス --------------------------------------- - -認証されたユーザーデータを取得するには認証コンポーネントのこちらを使用します :: - - $user = $this->Authentication->getIdentity(); - -リクエストインスタンスから直接ユーザーデータ を取得することもできます。:: - - $user = $request->getAttribute('identity'); - -ログイン状態を確認する -------------------------- - -認証処理が成功したかどうかは、結果オブジェクトにアクセスすることで確認できます。:: - - // 認証コンポーネントを使います。 - $result = $this->Authentication->getResult(); - - // リクエストオブジェクトを使います。 - $result = $request->getAttribute('authentication')->getResult(); - - if ($result->isValid()) { - $user = $request->getAttribute('identity'); - } else { - $this->log($result->getStatus()); - $this->log($result->getErrors()); - } - -結果セットは ``getStatus()`` から返されたオブジェクトの状態が、結果オブジェクトの中のこれらの定数のいずれかと一致します。: - -* ``ResultInterface::SUCCESS``, うまくいった場合。 -* ``ResultInterface::FAILURE_IDENTITY_NOT_FOUND``, 身元が不明の場合。 -* ``ResultInterface::FAILURE_CREDENTIALS_INVALID``, クレデンシャルが無効な場合。 -* ``ResultInterface::FAILURE_CREDENTIALS_MISSING``, クレデンシャルがリクエストに含まれていない場合。 -* ``ResultInterface::FAILURE_OTHER``, その他の種類の障害が発生した場合。 - -``getErrors()`` が返すエラー配列には、 -認証を試みた特定のシステムから得られる **追加の** 情報が含まれています。 -例えば、LDAPやOAuthなどは、その実装に特有のエラーをここに書き込むことで、 -原因のロギングやデバッグを容易にすることができます。 -しかし、同梱されている認証子のほとんどはここには何も入れていません。 - -identity のログアウト ------------------------- - -ログアウトするには:: - - $this->Authentication->logout(); - -もし、 ``logoutRedirect`` を設定しているならば、 -``Authentication::logout()`` はその値を返します。 -それ以外の場合は、 ``false`` を返します。 -どちらの場合も実際のリダイレクトは行われません。 - -あるいは、 コンポーネントの代わりに、サービスを使ってログアウトすることもできます :: - - $return = $request->getAttribute('authentication')->clearIdentity($request, $response); - -返される結果には、次のような配列が含まれます。:: - - [ - 'response' => object(Cake\Http\Response) { ... }, - 'request' => object(Cake\Http\ServerRequest) { ... }, - ] - -.. note:: - これはリクエストオブジェクトとレスポンスオブジェクトを含む配列を返します。 - 両方とも不変なので、新しいオブジェクトを取り戻すことができます。 - 変更されたレスポンスやリクエストオブジェクトを使い続けたい場合は、 - 作業しているコンテキストに応じて、今後はこれらのインスタンスを使用しなければならないでしょう。 - -自動Identityチェックを構成する ---------------------------------- - -デフォルトでは ``認証コンポーネント`` は、 ``Controller.startup`` -イベントの間に存在するIDを自動的に強制します。 -このチェックは ``Controller.initialize`` イベント中に適用することもできます:: - - // コントローラの initialize() メソッドの中です。 - $this->loadComponent('Authentication.Authentication', [ - 'identityCheckEvent' => 'Controller.initialize', - ]); - -また、 ``requireIdentity`` オプションを使って ID チェックを完全に無効にすることもできます。 diff --git a/docs/ja/authenticators.rst b/docs/ja/authenticators.rst deleted file mode 100644 index 834e8607..00000000 --- a/docs/ja/authenticators.rst +++ /dev/null @@ -1,365 +0,0 @@ -認証機能 -############## - -Authenticatorは、リクエストを認証操作に変換する処理を行います。 -それらは、 :doc:`/identifiers` を利用して、既知の :doc:`/identity-object` を見つけます。 - -`Session` -=========== - -この認証機能は、セッションにユーザー情報や資格情報が含まれているかどうかをチェックします。 -以下に挙げた ``Form`` のようなステートフルな認証機能を使う場合、 -ユーザーが一度ログインしたら、それ以降でセッションからデータを取得されるように、 -最初に ``Session`` の認証機能をロードするようにしてください。 -設定オプション: - -- **sessionKey**: ユーザーのセッションキー, デフォルトは ``Auth`` - -`Form` -========= - -リクエストボディのデータを調べます。通常、フォームの送信が POST / PUT 経由で行われる場合が多いです。 - -設定オプション: - -- **loginUrl**: ログインURL、文字列またはURLの配列。デフォルトは``null`` で、すべてのページがチェックされます。 -- **fields**: ``username`` と ``password`` を指定したPOSTフィールドに描画する配列です。 -- **urlChecker**: URLチェッカーのクラスまたはオブジェクト。デフォルトは ``DefaultUrlChecker``。 -- **useRegex**: URLのマッチングに正規表現を使うかどうか。 デフォルトは ``false``. -- **checkFullUrl**: クエリ文字を含むURLをチェックするかどうか。 - ログインフォームが別のサブドメインにある場合に便利です。 - デフォルトは、``false``。 - - -.. warning:: - URLに配列構文を使用した場合、URLはCakePHPルータによって生成されます。 - 結果は、ルート処理によってはリクエストURIに実際にあるものと **異なる** かもしれない。 - 大文字小文字を区別するために、これを考慮してください - -トークン -======== - -トークン認証機能は、ヘッダーやリクエストパラメータの中にある -リクエストと一緒に来るトークンに基づいてリクエストを認証することができます。 - -設定オプション: - -- **queryParam**: クエリパラメータの名前. クエリパラメータからトークンを取得したい場合に設定します。 -- **header**: ヘッダーの名前. ヘッダーからトークンを取得したい場合に設定します。 -- **tokenPrefix**: オプションのトークンプレフィックス。 - -ヘッダやクエリ文字列からトークンを取得する例は次のようになります:: - - $service->loadAuthenticator('Authentication.Token', [ - 'queryParam' => 'token', - 'header' => 'Authorization', - 'tokenPrefix' => 'Token' - ]); - -上記の場合、 ``Token`` の前にトークンとスペースがある限り、 ``token`` のGETパラメータまたは ``Authorization`` ヘッダが読み込まれます。 - -トークンは常に次のように設定された識別子に渡されます:: - - [ - 'token' => '{token-value}', - ] - -JWT -=== - -JWT 認証機能は、ヘッダーまたはクエリパラメータから `JWT token `__ を取得し、 -ペイロードを直接返すか、識別子に渡して別のデータソースなどと照合して検証します。 - -- **header**: トークンを確認するためのヘッダー行です。デフォルトは ``Authorization`` です。 -- **queryParam**: トークンをチェックするクエリパラメータ。デフォルトは ``token`` です。 -- **tokenPrefix**: prefixトークン. デフォルトは ``bearer`` です。 -- **algorithm**: Firebase JWT のハッシュアルゴリズム。デフォルトは ``'HS256'`` です。 -- **returnPayload**: 識別子を経由せずに、トークンのペイロードを直接返すか返さないか。デフォルトは ``true`` です。 -- **secretKey**: デフォルトは ``null`` ですが、秘密鍵を ``Security::salt()`` 提供しているCakePHPアプリケーションのコンテキストではない場合は **必須** です。 - -デフォルトでは、 ``JwtAuthenticator`` は対称鍵アルゴリズム ``HS256`` を使用し、暗号化鍵として、 -``Cake\Utility\Security::salt()`` の値を使用します。:: - - # 秘密鍵生成 - openssl genrsa -out config/jwt.key 1024 - # 公開鍵生成 - openssl rsa -in config/jwt.key -outform PEM -pubout -out config/jwt.pem - -``jwt.key`` ファイルは秘密鍵であり、安全に保管する必要があります。 -``jwt.pem`` ファイルは公開鍵です。 -このファイルは、モバイルアプリなどの外部アプリケーションによって作成されたトークンを -検証する必要がある場合に使用する必要があります。 - -以下の例では、 ``JwtSubject`` 識別しを用いてトークンの ``sub`` (subject) に基づいてユーザーを識別し、 -トークンの検証に公開鍵を使用するように、 ``Authenticator`` を設定しています。 - -``Application`` クラスに以下を追加してください:: - - public function getAuthenticationService(ServerRequestInterface $request): AuthenticationServiceInterface - { - $service = new AuthenticationService(); - // ... - $service->loadIdentifier('Authentication.JwtSubject'); - $service->loadAuthenticator('Authentication.Jwt', [ - 'secretKey' => file_get_contents(CONFIG . '/jwt.pem'), - 'algorithm' => 'RS256', - 'returnPayload' => false - ]); - } - -``UsersController`` に追加:: - - public function login() - { - $result = $this->Authentication->getResult(); - if ($result->isValid()) { - $privateKey = file_get_contents(CONFIG . '/jwt.key'); - $user = $result->getData(); - $payload = [ - 'iss' => 'myapp', - 'sub' => $user->id, - 'exp' => time() + 60, - ]; - $json = [ - 'token' => JWT::encode($payload, $privateKey, 'RS256'), - ]; - } else { - $this->response = $this->response->withStatus(401); - $json = []; - } - $this->set(compact('json')); - $this->viewBuilder()->setOption('serialize', 'json'); - } - -公開鍵ファイルを外部アプリに共有する以外にも、 -以下のようにアプリを設定することで、 -JWKSのエンドポイントを経由して公開鍵ファイルを配布することができます。:: - - // config/routes.php - $builder->setExtensions('json'); - $builder->connect('/.well-known/:controller/*', [ - 'action' => 'index', - ], [ - 'controller' => '(jwks)', - ]); // connect /.well-known/jwks.json to JwksController - - // controller/JwksController.php - public function index() - { - $pubKey = file_get_contents(CONFIG . './jwt.pem'); - $res = openssl_pkey_get_public($pubKey); - $detail = openssl_pkey_get_details($res); - $key = [ - 'kty' => 'RSA', - 'alg' => 'RS256', - 'use' => 'sig', - 'e' => JWT::urlsafeB64Encode($detail['rsa']['e']), - 'n' => JWT::urlsafeB64Encode($detail['rsa']['n']), - ]; - $keys['keys'][] = $key; - - $this->viewBuilder()->setClassName('Json'); - $this->set(compact('keys')); - $this->viewBuilder()->setOption('serialize', 'keys'); - } - -JWKSの詳細情報は https://datatracker.ietf.org/doc/html/rfc7517 -または https://auth0.com/docs/tokens/json-web-tokens/json-web-key-sets を参照してください。 - -Http基本 -========= - -https://en.wikipedia.org/wiki/Basic_access_authentication を確認してください。 - -設定オプション: - -- **realm**: デフォルトは ``$_SERVER['SERVER_NAME']`` で、必要に応じて上書きしてください。 - -Httpダイジェスト -================= - -https://en.wikipedia.org/wiki/Digest_access_authentication を確認してください。 - -設定オプション: - -- **realm**: デフォルトは ``null`` です。 -- **qop**: デフォルトは ``auth`` です。 -- **nonce**: デフォルトは ``uniqid(''),`` です。 -- **opaque**: デフォルトは ``null`` です。 - -クッキー認証機能 別名 "Remember Me" -====================================== - -クッキー認証機能を使用すると、ログインフォームに "remember me" 機能を実装することができます。 - -ログインフォームに、この認証機能で設定されているフィールド名と一致するフィールドがあることを確認してください。 - -クッキーを暗号化して複合化するには、AuthenticationMiddlewareの **前に** -EncryptedCookieMiddlewareをアプリに追加したことを確認してください。 - -設定オプション: - -- **rememberMeField**: デフォルトは ``remember_me`` です。 -- **cookie**: クッキーオプションの配列: - - - **name**: クッキー名, デフォルトは ``CookieAuth`` - - **expire**: 有効期限, デフォルトは ``null`` です。 - - **path**: パス, デフォルトは ``/`` です。 - - **domain**: ドメイン, デフォルトは空の文字列です \`\` - - **secure**: Bool, デフォルトは ``false`` です。 - - **httpOnly**: Bool, デフォルトは ``false`` です。 - - **value**: Value, デフォルトは空の文字列です。 \`\` - -- **fields**: ``username`` と ``password`` を指定されたIDフィールドにマップする配列 -- **urlChecker**: URLチェッカーのクラスまたはオブジェクト。デフォルトは ``DefaultUrlChecker`` -- **loginUrl**: ログイン URL, 文字列または URL の配列。 デフォルトは ``null`` で、すべてのページがチェックされます。 -- **passwordHasher**: トークンハッシュに使うパスワードハッシャー。デフォルトは ``DefaultPasswordHasher::class``. - -OAuth -===== - -現在のところ、OAuth認証機能の実装予定はありません。 -その主な理由は、OAuth 2.0が認証プロトコルではないからです。 - -このトピックについて知りたい場合は、 -`ここ `__. - -将来的にはOpenID Connect認証機能を追加するかもしれません。 - -イベント -========== - -認証によって発生するイベントは1つだけです。: -``Authentication.afterIdentify``. - -イベントとは何か、イベントの使い方がわからない場合は -`ここをクリックしてください! `__. - -身元の特定に成功した後に ``Authentication.afterIdentify`` イベントが -``AuthenticationComponent`` によって発行されます。 - -イベントには以下のデータが含まれています。: - -- **provider**: ``\Authentication\Authenticator\AuthenticatorInterface`` を実装したオブジェクトです。 -- **identity**: ``\ArrayAccess`` を実装したオブジェクトです。 -- **service**: ``\Authentication\AuthenticationServiceInterface`` を実装したオブジェクトです。 - -イベントのサブジェクトは、AuthenticationComponent がアタッチされている -現在のコントローラのインスタンスになります。 - -しかし、このイベントが発生するのは、IDを識別するために使用された -authenticator が永続的ではなく、ステートレスではない場合に限られます。 -これは、例えばセッション認証やトークンがリクエストのたびに毎回イベントを発生させてしまうからです。 - -含まれている認証子からは、FormAuthenticatorのみがイベントを発生させます。 -その後、セッション認証機能がIDを提供します。 - -URL チェッカー -================= - -``Form`` や ``Cookie`` のような認証証は、 ``/login`` ページのような -特定のページでのみ実行されるべきものがあります。 -これは、URLチェッカーを使用することで実現できます。 - -デフォルトは ``DefaultUrlChecker`` を使います。 -これは、正規表現チェックをサポートした文字列URLを比較に使用します。 - -設定オプション: - -- **useRegex**: URLのマッチングに正規表現を使用するかどうか。デフォルトは ``false`` です。 -- **checkFullUrl**: フルURLをチェックするかどうか。 - ログインフォームが別のサブドメインにある場合に便利です。 - デフォルトは ``false`` です。 - -フレームワーク固有の URL のサポートが必要な場合など、カスタム URL チェッカーを実装することができます。 -この場合は ``Authentication\UrlChecker\UrlCheckerInterface`` を実装してください。 - -もっと詳しくURLチェッカーについて知るには :doc:`このページを見てください `. - -成功した Authenticator または Identifier の取得 -================================================== - -ユーザーの認証が完了した後、ユーザーの認証に成功した Authenticator を確認したり、 -次のような操作を行うことができます。:: - - // コントローラー、アクションの中 - $service = $this->request->getAttribute('authentication'); - - // 認証に失敗した場合、または認証機能がある場合は null になります。 - $authenticator = $service->getAuthenticationProvider(); - -ユーザーを特定した識別子も取得できます。:: - - // コントローラー、アクションの中 - $service = $this->request->getAttribute('authentication'); - - // 認証に失敗した場合は null になります。 - $identifier = $service->getIdentificationProvider(); - - -ステートフル認証でステートレス認証を使用する -================================================== - -``Token`` や ``HttpBasic`` を使用している場合は、他の認証しと一緒に ``HttpDigest`` を使用します。 -これらの認証子は、認証証明書が見つからないか無効な場合にリクエストを停止することを覚えておくべきです。 -これは、これらの認証子がレスポンスの中で特定のチャレンジヘッダを送信しなければならないために必要です:: - - use Authentication\AuthenticationService; - - // サービスのインスタンス化 - $service = new AuthenticationService(); - - // 識別子の読み込み - $service->loadIdentifier('Authentication.Password', [ - 'fields' => [ - 'username' => 'email', - 'password' => 'password' - ] - ]); - $service->loadIdentifier('Authentication.Token'); - - // Basicを最後にして、認証子をロードします。 - $service->loadAuthenticator('Authentication.Session'); - $service->loadAuthenticator('Authentication.Form'); - $service->loadAuthenticator('Authentication.HttpBasic'); - -もし ``HttpBasic`` や ``HttpDigest`` と他の認証子を組み合わせたい場合は、 -これらの認証子はリクエストを中止してブラウザのダイアログを強制的に表示するので注意してください。 - -認証されていないエラーの処理 -================================ - -認証されていないユーザがいた場合、 ``AuthenticationComponent`` は例外を発生させます。 -この例外をリダイレクトに変換するには ``AuthenticationService`` を設定する際に -``unauthenticatedRedirect`` を使ってください。 - -また、 ``queryParam`` オプションを使って現在のリクエストのターゲットURIを -クエリパラメータとして渡すこともできます:: - - // src/Application.phpのgetAuthenticationService() メソッドの中 - - $service = new AuthenticationService(); - - // 認証されていないときにリダイレクトする - $service->setConfig([ - 'unauthenticatedRedirect' => '/users/login', - 'queryParam' => 'redirect', - ]); - -そして、コントローラのログインメソッドの中で``getLoginRedirect()`` -を使うことで、クエリ文字列パラメータ:からリダイレクト先を安全に取得することができます。:: - - public function login() - { - $result = $this->Authentication->getResult(); - - // Regardless of POST or GET, redirect if user is logged in - if ($result->isValid()) { - // Use the redirect parameter if present. - $target = $this->Authentication->getLoginRedirect(); - if (!$target) { - $target = ['controller' => 'Pages', 'action' => 'display', 'home']; - } - return $this->redirect($target); - } - } diff --git a/docs/ja/conf.py b/docs/ja/conf.py deleted file mode 100644 index 5871da64..00000000 --- a/docs/ja/conf.py +++ /dev/null @@ -1,9 +0,0 @@ -import sys, os - -# Append the top level directory of the docs, so we can import from the config dir. -sys.path.insert(0, os.path.abspath('..')) - -# Pull in all the configuration options defined in the global config file.. -from config.all import * - -language = 'ja' diff --git a/docs/ja/contents.rst b/docs/ja/contents.rst deleted file mode 100644 index c99baef5..00000000 --- a/docs/ja/contents.rst +++ /dev/null @@ -1,18 +0,0 @@ -Contents -######## - -.. toctree:: - :maxdepth: 2 - :caption: CakePHP Authentication - - /index - /authenticators - /identifiers - /password-hashers - /identity-object - /middleware - /authentication-component - /migration-from-the-authcomponent - /testing - /url-checkers - /view-helper diff --git a/docs/ja/identifiers.rst b/docs/ja/identifiers.rst deleted file mode 100644 index 84e49c72..00000000 --- a/docs/ja/identifiers.rst +++ /dev/null @@ -1,165 +0,0 @@ -Identifiers -########### - -Identifiersは認証者がリクエストから抽出した情報に基づいてユーザやサービスを識別します。 -Identifiersは ``loadIdentifier`` メソッドでオプション指定することができます。 -パスワード識別子を使用した全体的な例は次のようになります。:: - - $service->loadIdentifier('Authentication.Password', [ - 'fields' => [ - 'username' => 'email', - 'password' => 'passwd', - ], - 'resolver' => [ - 'className' => 'Authentication.Orm', - 'finder' => 'active' - ], - 'passwordHasher' => [ - 'className' => 'Authentication.Fallback', - 'hashers' => [ - 'Authentication.Default', - [ - 'className' => 'Authentication.Legacy', - 'hashType' => 'md5' - ], - ] - ] - ]); - -Password -======== - -パスワード識別子は、渡された資格情報をデータソースに対してチェックします。 - -設定オプション: - -- **fields**: 調べるためのフィールドです。デフォルトは ``['username' => 'username', 'password' => 'password']``。 - ``ユーザー名`` を配列にすることができます。 - 例えば、 ``['username' => ['username', 'email'], 'password' => 'password']`` を使うと、 - ユーザ名とemailのどちらかのカラムの値をマッチさせることができます。 -- **resolver**: 同一性を決意します. デフォルトは ``Authentication.Orm`` で、CakePHPのORMを使用しています。 -- **passwordHasher**: パスワードハッシャー. デフォルトは、 - ``DefaultPasswordHasher::class`` 。 - -トークン -========== - -渡されたトークンをデータソースと照合します。 - -設定オプション: - -- **tokenField**: データベース内のフィールドをチェックします。 デフォルトは ``token`` です。 -- **dataField**: 認証器から渡されたデータの中のフィールド。デフォルトは ``token`` です。 -- **resolver**: 同一性を決意します。デフォルトは ``Authentication.Orm`` で、CakePHPのORMを使用しています。 - -JWT サブジェクト -================= - -渡された JWT トークンをデータソースと照合します。 - -- **tokenField**: データベース内のフィールドをチェックします。デフォルトは ``id`` です。 -- **dataField**: ユーザ識別子を取得するためのペイロードキー。デフォルトは、 ``sub`` です。 -- **resolver**: 同一性を決意します。デフォルトは ``Authentication.Orm`` で、CakePHPのORMを使用しています。 - -LDAP -==== - -LDAP サーバーに対して渡された資格情報をチェックします。 この識別子には PHP LDAP 拡張モジュールが必要です。 - -- **fields**: 調べるためのフィールドです。 デフォルトは ``['username' => 'username', 'password' => 'password']`` です。 -- **host**: LDAPサーバーのFQDN。 -- **port**: LDAPサバーのポート。デフォルトは ``389`` 。 -- **bindDN**: 認証するユーザーの識別名。呼び出し可能である必要があります。匿名バインドはサポートされていません。 -- **ldap**: 延長アダプターです。 デフォルトは ``\Authentication\Identifier\Ldap\ExtensionAdapter``。 - もしそれが ``AdapterInterface`` を実装しているならば、そのobject/classnameをここに渡すことができます。 -- **options**: 付加的な LDAP オプション, like ``LDAP_OPT_PROTOCOL_VERSION`` または ``LDAP_OPT_NETWORK_TIMEOUT`` のようなものです。 - `php.net `__ をみてより多くのオプションを参照してください。 - -コールバック -============ - -識別のためにコールバックを使用できるようにします。 シンプルな識別子やクイックプロトタイピングに便利です。 - -設定オプション: - -- **callback**: デフォルトは ``null`` で例外が発生します。 - 認証機能を使用するには、このオプションに有効なコールバックを渡す必要があります。 - -コールバックの識別子は、単純な結果を得るために ``null|ArrayAccess`` を返すこともできます。, -または、エラーメッセージを転送したい場合、 ``Authentication\Authenticator\Result`` を使用します。:: - - // シンプルなコールバック識別子 - $authenticationService->loadIdentifier('Authentication.Callback', [ - 'callback' => function($data) { - // 識別子のロジック - - // 識別されたユーザの配列を返すか、失敗した場合はnullを返します。 - if ($result) { - return $result; - } - - return null; - } - ]); - - // エラーメッセージを返すために結果オブジェクトを使用します。 - $authenticationService->loadIdentifier('Authentication.Callback', [ - 'callback' => function($data) { - // 識別子のロジック - - if ($result) { - return new Result($result, Result::SUCCESS); - } - - return new Result( - null, - Result::FAILURE_OTHER, - ['message' => 'Removed user.'] - ); - } - ]); - - -Identity resolvers -====================== - -Identity resolvers は、異なるデータソース用のアダプタを提供する。 -これにより、どのソースのアイデンティティを検索するかを制御することができます。 -これらは識別子とは別個のものであり、 -識別子の方法(form, jwt, basic auth)とは独立してスワップアウトできるようになっています。 - -ORM Resolver ------------------- - -CakePHP ORM の Identity resolvers。 - -設定オプション: - -- **userModel**: ユーザーモデルのアイデンティティが配置されています。 デフォルトは ``Users`` 。 -- **finder**: モデルと一緒に使うファインダー. デフォルトは ``all`` 。 - -ORM resolverを使用するには ``composer.json`` ファイルの中に ``cakephp/orm`` が必要です。 - -独自のリゾルバを書く -------------------------- - -どんなORMやデータソースでも、リゾルバを作成することで認証に対応できるようにすることができます。 -リゾルバは、``App\Identifier\Resolver`` の名前空間の下に、 -``Authentication\Identifier\Resolver\ResolverInterface`` を実装しなければなりません。 - -リゾルバの設定は``resolver`` のconfigオプションを使って行えます。:: - - $service->loadIdentifier('Authentication.Password', [ - 'resolver' => [ - // name: \Some\Other\Custom\Resolver::class フルのクラス名です。 - 'className' => 'MyResolver', - // リゾルバのコンストラクタに追加のオプションを渡します。 - 'option' => 'value' - ] - ]); - -またはセッターを使用して注入してください。:: - - $resolver = new \App\Identifier\Resolver\CustomResolver(); - $identifier = $service->loadIdentifier('Authentication.Password'); - $identifier->setResolver($resolver); diff --git a/docs/ja/identity-object.rst b/docs/ja/identity-object.rst deleted file mode 100644 index 53bacc0c..00000000 --- a/docs/ja/identity-object.rst +++ /dev/null @@ -1,112 +0,0 @@ -Identity Objects -################ - -Identity オブジェクトは、認証サービスによって返され、リクエストで利用可能になる。 -Identitiesは現在のログインIDのプライマリIDの値を取得するためのメソッド ``getIdentifier()`` を提供しています。 - -このオブジェクトが存在する理由は、それを実装/ソースにするインターフェースを提供するためです:: - - // Service - $authenticationService - ->getIdentity() - ->getIdentifier() - - // Component - $this->Authentication - ->getIdentity() - ->getIdentifier(); - - // Request - $this->request - ->getAttribute('identity') - ->getIdentifier(); - -identityオブジェクトはArrayAccessを提供していますが、 -データにアクセスするための ``get()`` メソッドも提供しています。 -getメソッドはフィールドマッピングを認識しているので、 -配列へのアクセスよりも ``get()`` メソッドを使うことを強く推奨します。:: - - $identity->get('email'); - $identity->get('username'); - -また、 ``get`` メソッドはIDEのメタファイルを介してタイプヒントを与えることもできます。 -例えば、 `IdeHelper `__ 。 - -もしあなたが望むならば、プロパティアクセスを使用することができます。:: - - $identity->email; - $identity->username; - -デフォルトの Identity オブジェクトクラスは、フィールドをマップするように構成できます。 -これは、ID の識別子が型にはまらない ``id`` フィールドである場合や、 -他のフィールドをより一般的で一般的な名前にマップしたい場合に非常に便利です。:: - - $identity = new Identity($data, [ - 'fieldMap' => [ - 'id' => 'uid', - 'username' => 'first_name' - ] - ]); - -独自のIdentityオブジェクトの作成 ---------------------------------- - -デフォルトでは、Authentication プラグインは -メソッドとプロパティアクセスをプロキシする ``IdentityDecorator`` で返されたユーザーデータをラップします。 -自分の ID オブジェクトを作りたい場合は ``IdentityInterface`` を実装しなければなりません。 - -User クラスへの IdentityInterface の実装 ------------------------------------------------------ - -このプラグインで既存のUserクラスを使い続けたい場合は、 ``Authentication\IdentityInterface`` を実装してください:: - - namespace App\Model\Entity; - - use Authentication\IdentityInterface; - use Cake\ORM\Entity; - - class User extends Entity implements IdentityInterface - { - /** - * Authentication\IdentityInterface method - */ - public function getIdentifier() - { - return $this->id; - } - - /** - * Authentication\IdentityInterface method - */ - public function getOriginalData() - { - return $this; - } - - // Other methods - } - -カスタムIdentityデコレータを使用する ------------------------------------- - -もしあなたの識別子が ``IdentityInterface`` を実装したオブジェクトに変更を加えることができない場合は、 -必要なインターフェイスを実装したカスタムデコレータを実装することができます。:: - - // 呼び出し可能な... - $identityResolver = function ($data) { - return new MyCustomIdentity($data); - }; - - //...またはクラス名を指定して ID ラッパーを設定することができます。 - $identityResolver = MyCustomIdentity::class; - - // そしてそれをサービスの設定に渡します。 - $service = new AuthenticationService([ - 'identityClass' => $identityResolver, - 'identifiers' => [ - 'Authentication.Password' - ], - 'authenticators' => [ - 'Authentication.Form' - ] - ]); diff --git a/docs/ja/index.rst b/docs/ja/index.rst deleted file mode 100644 index 574317d4..00000000 --- a/docs/ja/index.rst +++ /dev/null @@ -1,182 +0,0 @@ -Quick Start -########### - -CakePHPから `composer `_ でプラグインをインストールします。 -プロジェクトのルートディレクトリ( **composer.json** ファイルが置かれている場所です) - -.. code-block:: bash - - php composer.phar require "cakephp/authentication:^2.0" - - -プロジェクトの ``src/Application.php`` に以下の文を追加してプラグインをロードしてください。 :: - - public function bootstrap(): void - { - parent::bootstrap(); - - $this->addPlugin('Authentication'); - } - - -はじめに -=============== - -認証プラグインは、ミドルウェアとしてアプリケーションと統合します。 `middleware `_ -また、認証されていないアクセスをより簡単にするためのコンポーネントとして使用することもできます。 まずはミドルウェアを適用してみましょう。 - -**src/Application.php** に以下のクラスを追加します。 - -インポート:: - - use Authentication\AuthenticationService; - use Authentication\AuthenticationServiceInterface; - use Authentication\AuthenticationServiceProviderInterface; - use Authentication\Identifier\AbstractIdentifier; - use Authentication\Middleware\AuthenticationMiddleware; - use Cake\Http\MiddlewareQueue; - use Psr\Http\Message\ServerRequestInterface; - -次に、アプリケーションに実装されたインターフェースに ``AuthenticationProviderInterface`` を追加します。:: - - class Application extends BaseApplication implements AuthenticationServiceProviderInterface - -次に、 ``middleware()`` メソッドに以下の `AuthenticationMiddleware` を追加します。:: - - $middleware->add(new AuthenticationMiddleware($this)); - -.. note:: - 両方ある場合は ``AuthenticationMiddleware`` の前に ``AuthorizationMiddleware`` を追加するようにしてください。 - -リクエストの処理を開始すると、 ``AuthenticationMiddleware`` はフックメソッドを呼び出します。 -このフックメソッドにより、アプリケーションが使用したい ``AuthenticationService`` を定義することができます。 -以下のメソッドを **src/Application.php** に記述します。:: - - /** - * サービスプロバイダのインスタンスを返します。 - * - * @param \Psr\Http\Message\ServerRequestInterface $request Request - * @return \Authentication\AuthenticationServiceInterface - */ - public function getAuthenticationService(ServerRequestInterface $request): AuthenticationServiceInterface - { - $service = new AuthenticationService(); - - // 認証されていない場合にユーザーがどこにリダイレクトするかを定義します。 - $service->setConfig([ - 'unauthenticatedRedirect' => '/users/login', - 'queryParam' => 'redirect', - ]); - - $fields = [ - AbstractIdentifier::CREDENTIAL_USERNAME => 'email', - AbstractIdentifier::CREDENTIAL_PASSWORD => 'password' - ]; - - // 認証者を読み込みます。セッションを優先してください。 - $service->loadAuthenticator('Authentication.Session'); - $service->loadAuthenticator('Authentication.Form', [ - 'fields' => $fields, - 'loginUrl' => '/users/login' - ]); - - // 識別子を読み込みます。 - $service->loadIdentifier('Authentication.Password', compact('fields')); - - return $service; - } - - -まず、ユーザーが認証されていない場合にどうするかを設定します。 -次に、アプリケーションがユーザーを認証するための仕組みを定義する ``Session`` と ``Form`` :doc:`/authenticators` をアタッチします。 -``Session`` はセッション内のデータに基づいてユーザを識別し、 ``Form`` はログインフォームを ``loginUrl`` で扱うことを可能にします。 - -最後に、ログインしたユーザーを表す :doc:`identifier ` に変換するための :doc:`identity ` をアタッチします。 - -認証が確認できた場合、ミドルウェアは認証サービスを `属性 `_. としてリクエストオブジェクトに追加します。 - -次に、 ``AppController`` に :doc:`/authentication-component` を呼び出します。:: - - // in src/Controller/AppController.php - public function initialize() - { - parent::initialize(); - - $this->loadComponent('Authentication.Authentication'); - } - -デフォルトでコンポーネントは、 **全て** のアクションに認証済みのユーザーを必要とします。 -特定のコントローラでこの動作を無効にするには、 ``allowUnauthenticated()`` を使用してください。:: - - // コントローラの beforeFilter か initialize で、 - // ログインしている必要のないview()とindex()を作成します。 - $this->Authentication->allowUnauthenticated(['view', 'index']); - -ログインアクションの構成 -======================================== - -アプリケーションにミドルウェアを適用したら、ユーザーがログインするための方法が必要になります。 -基本的に次のように ``UsersController`` にloginアクションと追加します。:: - - public function login() - { - $result = $this->Authentication->getResult(); - // ユーザーがログインしている場合は、そのユーザーを送り出してください。 - if ($result->isValid()) { - $target = $this->Authentication->getLoginRedirect() ?? '/home'; - return $this->redirect($target); - } - if ($this->request->is('post')) { - $this->Flash->error('ユーザー名とパスワードが無効です'); - } - } - -前のセクションで述べたように、コントローラの ``beforeFilter()`` コールバックで ``login`` アクションにアクセスできるようにして、 -認証されていないユーザがアクセスできるようにしてください。:: - - public function beforeFilter(\Cake\Event\EventInterface $event) - { - parent::beforeFilter($event); - - $this->Authentication->allowUnauthenticated(['login']); - } - -シンプルなlogoutアクションの追加:: - - public function logout() - { - $this->Authentication->logout(); - return $this->redirect(['controller' => 'Users', 'action' => 'login']); - } - -ログインするためには、ユーザーはハッシュ化されたパスワードを持つ必要があります。以下のようなことができます。 -ユーザーがエンティティを使用してパスワードを更新すると、自動的にパスワードをハッシュ化します。:: - - // in src/Model/Entity/User.php - use Authentication\PasswordHasher\DefaultPasswordHasher; - - class User extends Entity - { - // ... other methods - - // Automatically hash passwords when they are changed. - protected function _setPassword(string $password) - { - $hasher = new DefaultPasswordHasher(); - return $hasher->hash($password); - } - } - - -Further Reading -=============== - -* :doc:`/authenticators` -* :doc:`/identifiers` -* :doc:`/password-hashers` -* :doc:`/identity-object` -* :doc:`/authentication-component` -* :doc:`/migration-from-the-authcomponent` -* :doc:`/url-checkers` -* :doc:`/testing` -* :doc:`/view-helper` diff --git a/docs/ja/middleware.rst b/docs/ja/middleware.rst deleted file mode 100644 index d4459389..00000000 --- a/docs/ja/middleware.rst +++ /dev/null @@ -1,56 +0,0 @@ -Middleware -########## - -``AuthenticationMiddleware`` は認証プラグインの中で成形しています。 -これは、アプリケーションへの各リクエストを捕らえ、いずれかの認証証明証でユーザーの認証を試みます。 -各認証機能は、ユーザが認証されるまで、あるいはユーザが見つからないまで順番に試行されます。 -認証された場合の ID と認証結果オブジェクトを含むリクエストには ``authentication`` 、``identity`` 、 ``authenticationResult`` -属性が設定され、認証子によって提供された追加のエラーを含むことができます。 - -各リクエストの最後に ``identity`` は ``Session`` のようなステートフルな認証機能に保持されます。 - -設定 -========= - -ミドルウェアの設定は全て ``AuthenticationService`` で行います。 -サービスでは、次の構成オプションを使用できます: - -- ``identityClass`` - IDのクラス名、または呼び出し可能なIDビルダー。 -- ``identityAttribute`` - ID を格納するために使用されるリクエスト属性。デフォルトは ``identity``。 -- ``unauthenticatedRedirect`` - 認証されていない場合リダイレクトするURL。 -- ``queryParam`` - 文字列を設定すると、認証されていないリダイレクトに - ``redirect`` クエリ文字列パラメータに以前にブロックされたURLが含まれるようになります。 - - -複数の認証設定の設定 -========================= - -アプリケーションがAPIやWeb UIなど、アプリケーションのさまざまな部分で異なる認証設定を必要とする場合。 -これはアプリケーションの ``getAuthenticationService()`` フックメソッドで条件付きロジックを使用することで可能です。 -リクエストオブジェクトを検査することで、認証を適切に設定することができます:: - - public function getAuthenticationService(ServerRequestInterface $request): AuthenticationServiceInterface - { - $path = $request->getPath(); - - $service = new AuthenticationService(); - if (strpos($path, '/api') === 0) { - // Accept API tokens only - $service->loadAuthenticator('Authentication.Token'); - $service->loadIdentifier('Authentication.Token'); - - return $service; - } - - // Web 認証 - // サポートセッションとフォームログイン - $service->loadAuthenticator('Authentication.Session'); - $service->loadAuthenticator('Authentication.Form'); - - $service->loadIdentifier('Authentication.Password'); - - return $service; - } - -上記の例ではパスプレフィックスを使用していますが、同様のロジックをサブドメインやドメイン、 -リクエストに存在するその他のヘッダや属性にも適用することができます。 diff --git a/docs/ja/migration-from-the-authcomponent.rst b/docs/ja/migration-from-the-authcomponent.rst deleted file mode 100644 index 328990f7..00000000 --- a/docs/ja/migration-from-the-authcomponent.rst +++ /dev/null @@ -1,293 +0,0 @@ -認証コンポーネントから移行 -################################ - -違い -=========== - -- このプラグインは意図的に認可を処理しません。 - `懸念事項 `__ を明確に分離するために、 - わざと認可から `切り離され `__ ました。 - `コンピュータアクセス制御`__ もご覧ください。 - このプラグインは、 *本人確認* と *認証のみ* を行います。 - 認可のための別のプラグインがあるかもしれません。 -- セッションの自動チェックはありません。 - セッションから実際のユーザーデータを取得するには、 ``SessionAuthenticator`` を使用する必要があります。 - 設定したセッションキーにデータがあるかどうかをチェックして、それをIDオブジェクトに入れてくれます。 -- ユーザーのデータは古いAuthComponentでは利用できなくなりましたが、 - request属性を使ってアクセスでき、 - identityオブジェクトにカプセル化されています: ``$request->getAttribute('authentication')->getIdentity();`` 。 - さらに, ``AuthenticationComponent`` の ``getIdentity()`` と ``getIdentityData()`` が使えます。 -- 認証処理のロジックは、認証と本人確認に分かれています。 - 認証機能はリクエストから認証情報を抽出し、識別子は認証情報を検証して一致するユーザーを見つけます。 -- DigestAuthenticateの名前がHttpDigestAuthenticatorに変更しました。 -- BasicAuthenticateの名前がHttpBasicAuthenticatorに変更しました。 - -類似 -======= - -- 既存の認証アダプタ、Form、Basic、Digestはすべて残っていますが、認証アダプタにリファクタリングされています。 - -認証装置と認証者 -============================== - -関係分離の原則に従って、従来の認証オブジェクトは、 -認証装置と認証者という別々のオブジェクトに分割されました。 - -- **認証装置** 受信したリクエストを受け取り、そこから識別情報を抽出しようとします。 - 資格情報が見つかった場合、その資格情報はユーザーの位置を示す識別子のコレクションに渡されます。 - このため、認証子はIdentifierCollectionをコンストラクタの最初の引数として取ります。 -- **認証者** ストレージシステムに対する識別資格情報を検証します。 - 例: (ORMテーブル, LDAP など) と識別されたユーザーデータを返します。 - -これにより、必要に応じて識別ロジックを変更したり、 -複数のユーザーデータソースを使用したりすることが容易になります。 - -独自の識別子を実装したい場合は ``IdentifierInterface`` を実装しなければなりません。 - -認証設定の移行 -======================== - -アプリケーションを移行する最初のステップは、 -アプリケーションのブートストラップメソッドで認証プラグインをロードすることです。:: - - public function bootstrap(): void - { - parent::bootstrap(); - $this->addPlugin('Authentication'); - } - -その後、アプリケーションを更新して、認証プロバイダのインターフェースを実装してください。 -これにより、AuthenticationMiddleware はアプリケーションから認証サービスを取得する方法を知ることができます。:: - - // src/Application.php の中 - - // 以下を追加してください - use Authentication\AuthenticationService; - use Authentication\AuthenticationServiceInterface; - use Authentication\AuthenticationServiceProviderInterface; - use Authentication\Middleware\AuthenticationMiddleware; - use Psr\Http\Message\ResponseInterface; - use Psr\Http\Message\ServerRequestInterface; - - // 認証インターフェースを追加します。 - class Application extends BaseApplication implements AuthenticationServiceProviderInterface - { - /** - * サービスプロバイダのインスタンスを返します。 - * - * @param \Psr\Http\Message\ServerRequestInterface $request Request - * @param \Psr\Http\Message\ResponseInterface $response Response - * @return \Authentication\AuthenticationServiceInterface - */ - public function getAuthenticationService(ServerRequestInterface $request) : AuthenticationServiceInterface - { - $service = new AuthenticationService(); - // サービス設定 (詳細は以下をご覧ください) - return $service; - } - } - -次に ``AuthenticationMiddleware`` を追加します。:: - - // src/Application.php のなか - public function middleware($middlewareQueue) - { - // その他、エラー処理やルーティングなどの各種ミドルウェアを追加しました。 - - // ミドルウェアキューにミドルウェアを追加する - $middlewareQueue->add(new AuthenticationMiddleware($this)); - - return $middlewareQueue; - } - -AuthComponent の設定の移行 ------------------------------- - -サービスを設定する際には ``AuthComponent`` の設定配列を識別子と認証子に分割する必要があります。 -また、このように ``AuthComponent`` を設定していたときは:: - - $this->loadComponent('Auth', [ - 'authentication' => [ - 'Form' => [ - 'fields' => [ - 'username' => 'email', - 'password' => 'password', - ] - ] - ] - ]); - -このように設定する必要があります。:: - - // サービスのインスタンス化 - $service = new AuthenticationService(); - - // 識別者の読み込み - $service->loadIdentifier('Authentication.Password', [ - 'fields' => [ - 'username' => 'email', - 'password' => 'password', - ] - ]); - - // 認証機能の読み込み - $service->loadAuthenticator('Authentication.Session'); - $service->loadAuthenticator('Authentication.Form'); - -もし ``userModel`` をカスタマイズしているならば、以下の設定を使うことができます。:: - - // サービスのインスタンス化 - $service = new AuthenticationService(); - - // 識別者の読み込み - $service->loadIdentifier('Authentication.Password', [ - 'resolver' => [ - 'className' => 'Authentication.Orm', - 'userModel' => 'Employees', - ], - 'fields' => [ - 'username' => 'email', - 'password' => 'password', - ] - ]); - -以前よりも少しコードが増えていますが、認証の処理方法に柔軟性が出てきています。 - -ロジックアクション --------------------- - -この``AuthenticationMiddleware``はあなたの認証子に基づいたアイデンティティのチェックと設定を行います。 -通常、ログイン後に ``AuthComponent`` は設定した場所にリダイレクトします。 -ログインが成功したときにリダイレクトするには、ログインアクションを変更して新しいIDの結果を確認してください。:: - - public function login() - { - $result = $this->Authentication->getResult(); - - // POSTかGETにかかわらず、ユーザーがログインしている場合はリダイレクト - if ($result->isValid()) { - $target = $this->Authentication->getLoginRedirect(); - return $this->redirect($target); - } - - // ユーザーの送信と認証に失敗した場合にエラーを表示する - if ($this->request->is(['post'])) { - $this->Flash->error('ユーザー名またはパスワードが無効です'); - } - } - -認証者の確認 -------------------- - -ミドルウェアを適用した後、``identity``リクエスト属性を使ってIDデータを利用することができます。 -これは今使っている ``$this->Auth->user()`` の呼び出しを置き換えるものです。 -現在のユーザーが認証されていないか、提供された資格情報が無効な場合は、 ``identity`` が ``null`` になります。:: - - $user = $request->getAttribute('identity'); - -認証処理の結果の詳細については、リクエストに添付されている結果オブジェクトにアクセスすることができ、 -``authentication`` 属性にアクセスすることはできません。:: - - $result = $request->getAttribute('authentication')->getResult(); - // 結果が有効な場合のブール値 - $isValid = $result->isValid(); - // ステータスコード - $statusCode = $result->getStatus(); - // 識別子が何かを提供した場合のエラーメッセージまたはデータの配列 - $errors = $result->getErrors(); - -これまで ``AuthComponent::setUser()`` を呼んでいた場所は、 -``setIdentity()`` を使うようにしてください。:: - - // アクセストークンでユーザーを読み取る必要があるとします。 - $user = $this->Users->find('byToken', ['token' => $token])->first(); - - // ユーザーを構成された認証子に持続させます。 - $this->Authentication->setIdentity($user); - - -許可/拒否ロジックの移行 --------------------------- - -``AuthComponent`` と同様に、 -``AuthenticationComponent`` は特定の動作を簡単に '公開' し、 -有効なIDを必要としないようにします。:: - - // In your controller's beforeFilter method. - $this->Authentication->allowUnauthenticated(['view']); - -Each call to ``allowUnauthenticated()`` will overwrite the current -action list. - -認証されていないリダイレクトの移行 -=================================== - -By default ``AuthComponent`` redirects users back to the login page when -authentication is required. In contrast, the ``AuthenticationComponent`` -in this plugin will raise an exception in this scenario. You can convert -this exception into a redirect using the ``unauthenticatedRedirect`` -when configuring the ``AuthenticationService``. - -You can also pass the current request target URI as a query parameter -using the ``queryParam`` option. Note that the redirect parameter is only -appended for GET requests to prevent redirecting to non-GET actions after login:: - - // In the getAuthenticationService() method of your src/Application.php - - $service = new AuthenticationService(); - - // Configure unauthenticated redirect - $service->setConfig([ - 'unauthenticatedRedirect' => '/users/login', - 'queryParam' => 'redirect', - ]); - -そして、コントローラのログインメソッドで ``getLoginRedirect()`` を使用して、 -クエリ文字列パラメータからリダイレクト先を安全に取得することができます。:: - - public function login() - { - $result = $this->Authentication->getResult(); - - // POSTかGETに関わらず、 ユーザーがログインしている場合はリダイレクト - if ($result->isValid()) { - // redirect パラメータがある場合は、それを使用します。 - $target = $this->Authentication->getLoginRedirect(); - if (!$target) { - $target = ['controller' => 'Pages', 'action' => 'display', 'home']; - } - return $this->redirect($target); - } - } - -ハッシングアップグレードロジックの移行 -======================================= - -アプリケーションが ``AuthComponent`` のハッシュアップグレード機能を使用している場合。 -このプラグインでは ``AuthenticationService`` を利用することで、そのロジックを複製することができます。:: - - public function login() - { - $result = $this->Authentication->getResult(); - - // POSTかGETに関わらず、 ユーザーがログインしている場合はリダイレクト - if ($result->isValid()) { - $authService = $this->Authentication->getAuthenticationService(); - - // 認証に使用された識別子を取得します。 - $identifier = $authService->getIdentificationProvider(); - if ($identifier !== null && $identifier->needsPasswordRehash()) { - // セーブ時にリハッシュが発生します。 - $user = $this->Users->get($this->Authentication->getIdentityData('id')); - $user->password = $this->request->getData('password'); - $this->Users->save($user); - } - - // ログインしたページにリダイレクトする - return $this->redirect([ - 'controller' => 'Pages', - 'action' => 'display', - 'home' - ]); - } - } diff --git a/docs/ja/password-hashers.rst b/docs/ja/password-hashers.rst deleted file mode 100644 index aab88615..00000000 --- a/docs/ja/password-hashers.rst +++ /dev/null @@ -1,79 +0,0 @@ -Password Hashers -################ - -Default -======= - -暗号化方法にPHPの定数 ``PASSWORD_DEFAULT`` を使用しています。 -デフォルトのハッシュ型は ``bcrypt`` です。 - - -こちらをご覧ください `PHPのドキュメント `__ -bcryptとPHPのパスワードハッシュについての詳細情報が書かれています。 - -このアダプタの設定オプションは次のとおりです: - -- **hashType**: 使用するハッシュ化アルゴリズム。 - 有効な値は ``password_hash()`` の引数 ``$algo`` でサポートされている値です。 - デフォルトは、 ``PASSWORD_DEFAULT`` です。 -- **hashOptions**: オプションの連想配列。 - 各ハッシュ型でサポートしているオプションについてはPHPマニュアルを参照してください。 - デフォルトは空の配列です。 - -レガシー -========= - -cakePHP2から移行したアプリケーションのためのパスワードハッシャーです。 - -フォールバック -================ - -フォールバックパスワードハッシャーでは、 -複数のハッシャーを設定することができ、それらを順次チェックしていきます。 -これにより、パスワードがリセットされて新しいハッシュにアップグレードされるまで、 -古いハッシュタイプでログインすることができます。 - -ハッシュアルゴリズムのアップグレード -==================================== - -CakePHPは、ユーザーのパスワードをあるアルゴリズムから別のアルゴリズムに移行するためのクリーンな方法を提供します。 -これは ``FallbackPasswordHasher`` クラスによって実現されます。 -レガシーパスワードからデフォルトのbcryptハッシャーに移行したい場合を想定しています。 -以下のようにフォールバックハッシュアーを想定することができます。:: - - $service->loadIdentifier('Authentication.Password', [ - // その他の設定オプション - 'passwordHasher' => [ - 'className' => 'Authentication.Fallback', - 'hashers' => [ - 'Authentication.Default', - [ - 'className' => 'Authentication.Legacy', - 'hashType' => 'md5', - 'salt' => false // saltのデフォルトをoffにする - ], - ] - ] - ]); - -ログインアクションの中で認証サービスを使って ``Password`` 識別子にアクセスし、 -現在のユーザーのパスワードをアップグレードする必要があるかどうかをチェックすることができます。:: - - public function login() - { - $authentication = $this->request->getAttribute('authentication'); - $result = $authentication->getResult(); - - // POST GETに関係なくログインする時にリダイレクトする - if ($result->isValid()) { - // 識別子に `Password` を使用していると仮定します。 - if ($authentication->identifiers()->get('Password')->needsPasswordRehash()) { - // セーブ時にリハッシュが発生します。 - $user = $this->Users->get($this->Auth->user('id')); - $user->password = $this->request->getData('password'); - $this->Users->save($user); - } - - // テンプレートをリダイレクトしたり、表示したりします。 - } - } diff --git a/docs/ja/testing.rst b/docs/ja/testing.rst deleted file mode 100644 index 063d58bc..00000000 --- a/docs/ja/testing.rst +++ /dev/null @@ -1,45 +0,0 @@ -認証によるテスト -###################### - -アプリケーションで ``authentication`` ミドルウェアがアクティブな状態であれば、 -統合テストで認証情報をシミュレートする必要があります。 -使用している認証の種類に応じて、クレデンシャルを異なる方法でシミュレートする必要があります。 -認証の一般的なタイプをいくつか確認してみましょう。 - -セッションベースの認証 -============================ - -セッションベースの認証では、通常セッションで見つかるであろうユーザデータをシミュレートする必要があります。 -テストケースでは、「ログイン」するためのヘルパーメソッドを定義することができます。:: - - protected function login($userId = 1) - { - $users = TableRegistry::getTableLocator()->get('Users'); - $user = $users->get($userId); - $this->session(['Auth' => $user]); - } - -統合テストでは ``login()`` を使ってユーザがログインしたときのシミュレーションをすることができます。:: - - public function testGet() - { - $this->login(); - $this->get('/bookmarks/1'); - $this->assertResponseOk(); - } - -トークンベースの認証 -========================== - -トークンベースの認証では ``Authorization`` ヘッダーをシミュレートする必要があります。 -有効なトークンを取得した後、リクエスト:: - - public function testGet() - { - $token = $this->getToken(); - $this->configRequest([ - 'headers' => ['Authorization' => 'Bearer ' . $token] - ]); - $this->get('/api/bookmarks'); - $this->assertResponseOk(); - } diff --git a/docs/ja/url-checkers.rst b/docs/ja/url-checkers.rst deleted file mode 100644 index 7c77b079..00000000 --- a/docs/ja/url-checkers.rst +++ /dev/null @@ -1,35 +0,0 @@ -URL チェッカー -################### - -抽象的でフレームワークに依存しないソリューションを提供するために、 -必要に応じて現在のURLの比較をカスタマイズできるURLチェッカーが実装されています。 -例えば、別のフレームワークのルーティングなど。 - -付属のチェッカー -================= - -DefaultUrlChecker ------------------ - -デフォルトのチェッカーでは、正規表現や文字列のURLでURLを比較することができます。 - -オプション: - -- **checkFullUrl**: フルURLを比較するには, プロトコルを含みます。 - ホストとポートの有無を指定します。 デフォルトは ``false`` です。 -- **useRegex**: チェッカーの引数 ``$loginUrls`` で指定された正規表現でURLを比較します。 - -CakeRouterUrlChecker --------------------- - -オプション: - -CakePHPsのルーティングシステムの配列表記を使用したい場合は、このチェッカーを使用します。 -チェッカーは名前付きルートでも動作します。 - -- **checkFullUrl**: プロトコル、ホスト、ポートを含む完全なURLを比較するかどうか。デフォルトは ``false`` - -独自のチェッカーの実装 ------------------------------ - -URLチェッカーは ``UrlCheckterInterface`` を実装 *していなければなりません* 。 diff --git a/docs/ja/view-helper.rst b/docs/ja/view-helper.rst deleted file mode 100644 index 640e0ff6..00000000 --- a/docs/ja/view-helper.rst +++ /dev/null @@ -1,24 +0,0 @@ -View Helper -=========== - -AppViewでヘルパーを次のようにロードします。:: - - $this->loadHelper('Authentication.Identity'); - -ユーザーがログインしているかどうかを非常に簡単に確認するには:: - - if ($this->Identity->isLoggedIn()) { - ... - } - -ユーザーデータの取得は:: - - $username = $this->Identity->get('username'); - -次のチェックは、あるユーザーに属するレコードが現在ログインしているユーザーであるかどうかを判断し、 -他のフィールドを比較するために使用することができます。:: - - $isCurrentUser = $this->Identity->is($user->id); - $isCurrentRole = $this->Identity->is($user->role_id, 'role_id'); - -このメソッドは主に単純なケースのための便利なメソッドであり、いかなる種類の適切な認可実装を置き換えることを意図したものではありません。 diff --git a/docs/package-lock.json b/docs/package-lock.json new file mode 100644 index 00000000..90214b3d --- /dev/null +++ b/docs/package-lock.json @@ -0,0 +1,2099 @@ +{ + "name": "docs", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "docs", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "@cakephp/docs-skeleton": "github:cakephp/docs-skeleton#node-package", + "vitepress": "^2.0.0-alpha.16" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.2.tgz", + "integrity": "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.0" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@cakephp/docs-skeleton": { + "version": "1.0.0", + "resolved": "git+ssh://git@github.com/cakephp/docs-skeleton.git#beb4b50177ce8fd81d5113d2e3fc49b107ddf2f2", + "bin": { + "cakedocs": "bin/cakedocs.js" + }, + "peerDependencies": { + "vitepress": "^2.0.0-alpha.15" + } + }, + "node_modules/@docsearch/css": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/@docsearch/css/-/css-4.6.2.tgz", + "integrity": "sha512-fH/cn8BjEEdM2nJdjNMHIvOVYupG6AIDtFVDgIZrNzdCSj4KXr9kd+hsehqsNGYjpUjObeKYKvgy/IwCb1jZYQ==", + "license": "MIT" + }, + "node_modules/@docsearch/js": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/@docsearch/js/-/js-4.6.2.tgz", + "integrity": "sha512-qj1yoxl3y4GKoK7+VM6fq/rQqPnvUmg3IKzJ9x0VzN14QVzdB/SG/J6VfV1BWT5RcPUFxIcVwoY1fwHM2fSRRw==", + "license": "MIT" + }, + "node_modules/@docsearch/sidepanel-js": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/@docsearch/sidepanel-js/-/sidepanel-js-4.6.2.tgz", + "integrity": "sha512-Pni85AP/GwRj7fFg8cBJp0U04tzbueBvWSd3gysgnOsVnQVSZwSYncfErUScLE1CAtR+qocPDFjmYR9AMRNJtQ==", + "license": "MIT" + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.4.tgz", + "integrity": "sha512-cQPwL2mp2nSmHHJlCyoXgHGhbEPMrEEU5xhkcy3Hs/O7nGZqEpZ2sUtLaL9MORLtDfRvVl2/3PAuEkYZH0Ty8Q==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.4.tgz", + "integrity": "sha512-X9bUgvxiC8CHAGKYufLIHGXPJWnr0OCdR0anD2e21vdvgCI8lIfqFbnoeOz7lBjdrAGUhqLZLcQo6MLhTO2DKQ==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.4.tgz", + "integrity": "sha512-gdLscB7v75wRfu7QSm/zg6Rx29VLdy9eTr2t44sfTW7CxwAtQghZ4ZnqHk3/ogz7xao0QAgrkradbBzcqFPasw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.4.tgz", + "integrity": "sha512-PzPFnBNVF292sfpfhiyiXCGSn9HZg5BcAz+ivBuSsl6Rk4ga1oEXAamhOXRFyMcjwr2DVtm40G65N3GLeH1Lvw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.4.tgz", + "integrity": "sha512-b7xaGIwdJlht8ZFCvMkpDN6uiSmnxxK56N2GDTMYPr2/gzvfdQN8rTfBsvVKmIVY/X7EM+/hJKEIbbHs9oA4tQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.4.tgz", + "integrity": "sha512-sR+OiKLwd15nmCdqpXMnuJ9W2kpy0KigzqScqHI3Hqwr7IXxBp3Yva+yJwoqh7rE8V77tdoheRYataNKL4QrPw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.4.tgz", + "integrity": "sha512-jnfpKe+p79tCnm4GVav68A7tUFeKQwQyLgESwEAUzyxk/TJr4QdGog9sqWNcUbr/bZt/O/HXouspuQDd9JxFSw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.4.tgz", + "integrity": "sha512-2kb4ceA/CpfUrIcTUl1wrP/9ad9Atrp5J94Lq69w7UwOMolPIGrfLSvAKJp0RTvkPPyn6CIWrNy13kyLikZRZQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.4.tgz", + "integrity": "sha512-aBYgcIxX/wd5n2ys0yESGeYMGF+pv6g0DhZr3G1ZG4jMfruU9Tl1i2Z+Wnj9/KjGz1lTLCcorqE2viePZqj4Eg==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.4.tgz", + "integrity": "sha512-7nQOttdzVGth1iz57kxg9uCz57dxQLHWxopL6mYuYthohPKEK0vU0C3O21CcBK6KDlkYVcnDXY099HcCDXd9dA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.4.tgz", + "integrity": "sha512-oPtixtAIzgvzYcKBQM/qZ3R+9TEUd1aNJQu0HhGyqtx6oS7qTpvjheIWBbes4+qu1bNlo2V4cbkISr8q6gRBFA==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.4.tgz", + "integrity": "sha512-8mL/vh8qeCoRcFH2nM8wm5uJP+ZcVYGGayMavi8GmRJjuI3g1v6Z7Ni0JJKAJW+m0EtUuARb6Lmp4hMjzCBWzA==", + "cpu": [ + "loong64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.4.tgz", + "integrity": "sha512-1RdrWFFiiLIW7LQq9Q2NES+HiD4NyT8Itj9AUeCl0IVCA459WnPhREKgwrpaIfTOe+/2rdntisegiPWn/r/aAw==", + "cpu": [ + "mips64el" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.4.tgz", + "integrity": "sha512-tLCwNG47l3sd9lpfyx9LAGEGItCUeRCWeAx6x2Jmbav65nAwoPXfewtAdtbtit/pJFLUWOhpv0FpS6GQAmPrHA==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.4.tgz", + "integrity": "sha512-BnASypppbUWyqjd1KIpU4AUBiIhVr6YlHx/cnPgqEkNoVOhHg+YiSVxM1RLfiy4t9cAulbRGTNCKOcqHrEQLIw==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.4.tgz", + "integrity": "sha512-+eUqgb/Z7vxVLezG8bVB9SfBie89gMueS+I0xYh2tJdw3vqA/0ImZJ2ROeWwVJN59ihBeZ7Tu92dF/5dy5FttA==", + "cpu": [ + "s390x" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.4.tgz", + "integrity": "sha512-S5qOXrKV8BQEzJPVxAwnryi2+Iq5pB40gTEIT69BQONqR7JH1EPIcQ/Uiv9mCnn05jff9umq/5nqzxlqTOg9NA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.4.tgz", + "integrity": "sha512-xHT8X4sb0GS8qTqiwzHqpY00C95DPAq7nAwX35Ie/s+LO9830hrMd3oX0ZMKLvy7vsonee73x0lmcdOVXFzd6Q==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.4.tgz", + "integrity": "sha512-RugOvOdXfdyi5Tyv40kgQnI0byv66BFgAqjdgtAKqHoZTbTF2QqfQrFwa7cHEORJf6X2ht+l9ABLMP0dnKYsgg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.4.tgz", + "integrity": "sha512-2MyL3IAaTX+1/qP0O1SwskwcwCoOI4kV2IBX1xYnDDqthmq5ArrW94qSIKCAuRraMgPOmG0RDTA74mzYNQA9ow==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.4.tgz", + "integrity": "sha512-u8fg/jQ5aQDfsnIV6+KwLOf1CmJnfu1ShpwqdwC0uA7ZPwFws55Ngc12vBdeUdnuWoQYx/SOQLGDcdlfXhYmXQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.4.tgz", + "integrity": "sha512-JkTZrl6VbyO8lDQO3yv26nNr2RM2yZzNrNHEsj9bm6dOwwu9OYN28CjzZkH57bh4w0I2F7IodpQvUAEd1mbWXg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.4.tgz", + "integrity": "sha512-/gOzgaewZJfeJTlsWhvUEmUG4tWEY2Spp5M20INYRg2ZKl9QPO3QEEgPeRtLjEWSW8FilRNacPOg8R1uaYkA6g==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.4.tgz", + "integrity": "sha512-Z9SExBg2y32smoDQdf1HRwHRt6vAHLXcxD2uGgO/v2jK7Y718Ix4ndsbNMU/+1Qiem9OiOdaqitioZwxivhXYg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.4.tgz", + "integrity": "sha512-DAyGLS0Jz5G5iixEbMHi5KdiApqHBWMGzTtMiJ72ZOLhbu/bzxgAe8Ue8CTS3n3HbIUHQz/L51yMdGMeoxXNJw==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.4.tgz", + "integrity": "sha512-+knoa0BDoeXgkNvvV1vvbZX4+hizelrkwmGJBdT17t8FNPwG2lKemmuMZlmaNQ3ws3DKKCxpb4zRZEIp3UxFCg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@iconify-json/simple-icons": { + "version": "1.2.75", + "resolved": "https://registry.npmjs.org/@iconify-json/simple-icons/-/simple-icons-1.2.75.tgz", + "integrity": "sha512-KvcCUbvcBWb0sbqLIxHoY8z5/piXY08wcY9gfMhF+ph3AfzGMaSmZFkUY71HSXAljQngXkgs4bdKdekO0HQWvg==", + "license": "CC0-1.0", + "dependencies": { + "@iconify/types": "*" + } + }, + "node_modules/@iconify/types": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@iconify/types/-/types-2.0.0.tgz", + "integrity": "sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==", + "license": "MIT" + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "license": "MIT" + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-rc.2", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.2.tgz", + "integrity": "sha512-izyXV/v+cHiRfozX62W9htOAvwMo4/bXKDrQ+vom1L1qRuexPock/7VZDAhnpHCLNejd3NJ6hiab+tO0D44Rgw==", + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.0.tgz", + "integrity": "sha512-WOhNW9K8bR3kf4zLxbfg6Pxu2ybOUbB2AjMDHSQx86LIF4rH4Ft7vmMwNt0loO0eonglSNy4cpD3MKXXKQu0/A==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.60.0.tgz", + "integrity": "sha512-u6JHLll5QKRvjciE78bQXDmqRqNs5M/3GVqZeMwvmjaNODJih/WIrJlFVEihvV0MiYFmd+ZyPr9wxOVbPAG2Iw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.60.0.tgz", + "integrity": "sha512-qEF7CsKKzSRc20Ciu2Zw1wRrBz4g56F7r/vRwY430UPp/nt1x21Q/fpJ9N5l47WWvJlkNCPJz3QRVw008fi7yA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.60.0.tgz", + "integrity": "sha512-WADYozJ4QCnXCH4wPB+3FuGmDPoFseVCUrANmA5LWwGmC6FL14BWC7pcq+FstOZv3baGX65tZ378uT6WG8ynTw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.60.0.tgz", + "integrity": "sha512-6b8wGHJlDrGeSE3aH5mGNHBjA0TTkxdoNHik5EkvPHCt351XnigA4pS7Wsj/Eo9Y8RBU6f35cjN9SYmCFBtzxw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.60.0.tgz", + "integrity": "sha512-h25Ga0t4jaylMB8M/JKAyrvvfxGRjnPQIR8lnCayyzEjEOx2EJIlIiMbhpWxDRKGKF8jbNH01NnN663dH638mA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.60.0.tgz", + "integrity": "sha512-RzeBwv0B3qtVBWtcuABtSuCzToo2IEAIQrcyB/b2zMvBWVbjo8bZDjACUpnaafaxhTw2W+imQbP2BD1usasK4g==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.60.0.tgz", + "integrity": "sha512-Sf7zusNI2CIU1HLzuu9Tc5YGAHEZs5Lu7N1ssJG4Tkw6e0MEsN7NdjUDDfGNHy2IU+ENyWT+L2obgWiguWibWQ==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.60.0.tgz", + "integrity": "sha512-DX2x7CMcrJzsE91q7/O02IJQ5/aLkVtYFryqCjduJhUfGKG6yJV8hxaw8pZa93lLEpPTP/ohdN4wFz7yp/ry9A==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.60.0.tgz", + "integrity": "sha512-09EL+yFVbJZlhcQfShpswwRZ0Rg+z/CsSELFCnPt3iK+iqwGsI4zht3secj5vLEs957QvFFXnzAT0FFPIxSrkQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.60.0.tgz", + "integrity": "sha512-i9IcCMPr3EXm8EQg5jnja0Zyc1iFxJjZWlb4wr7U2Wx/GrddOuEafxRdMPRYVaXjgbhvqalp6np07hN1w9kAKw==", + "cpu": [ + "loong64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.60.0.tgz", + "integrity": "sha512-DGzdJK9kyJ+B78MCkWeGnpXJ91tK/iKA6HwHxF4TAlPIY7GXEvMe8hBFRgdrR9Ly4qebR/7gfUs9y2IoaVEyog==", + "cpu": [ + "loong64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.60.0.tgz", + "integrity": "sha512-RwpnLsqC8qbS8z1H1AxBA1H6qknR4YpPR9w2XX0vo2Sz10miu57PkNcnHVaZkbqyw/kUWfKMI73jhmfi9BRMUQ==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.60.0.tgz", + "integrity": "sha512-Z8pPf54Ly3aqtdWC3G4rFigZgNvd+qJlOE52fmko3KST9SoGfAdSRCwyoyG05q1HrrAblLbk1/PSIV+80/pxLg==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.60.0.tgz", + "integrity": "sha512-3a3qQustp3COCGvnP4SvrMHnPQ9d1vzCakQVRTliaz8cIp/wULGjiGpbcqrkv0WrHTEp8bQD/B3HBjzujVWLOA==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.60.0.tgz", + "integrity": "sha512-pjZDsVH/1VsghMJ2/kAaxt6dL0psT6ZexQVrijczOf+PeP2BUqTHYejk3l6TlPRydggINOeNRhvpLa0AYpCWSQ==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.60.0.tgz", + "integrity": "sha512-3ObQs0BhvPgiUVZrN7gqCSvmFuMWvWvsjG5ayJ3Lraqv+2KhOsp+pUbigqbeWqueGIsnn+09HBw27rJ+gYK4VQ==", + "cpu": [ + "s390x" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.60.0.tgz", + "integrity": "sha512-EtylprDtQPdS5rXvAayrNDYoJhIz1/vzN2fEubo3yLE7tfAw+948dO0g4M0vkTVFhKojnF+n6C8bDNe+gDRdTg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.60.0.tgz", + "integrity": "sha512-k09oiRCi/bHU9UVFqD17r3eJR9bn03TyKraCrlz5ULFJGdJGi7VOmm9jl44vOJvRJ6P7WuBi/s2A97LxxHGIdw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.60.0.tgz", + "integrity": "sha512-1o/0/pIhozoSaDJoDcec+IVLbnRtQmHwPV730+AOD29lHEEo4F5BEUB24H0OBdhbBBDwIOSuf7vgg0Ywxdfiiw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.60.0.tgz", + "integrity": "sha512-pESDkos/PDzYwtyzB5p/UoNU/8fJo68vcXM9ZW2V0kjYayj1KaaUfi1NmTUTUpMn4UhU4gTuK8gIaFO4UGuMbA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.60.0.tgz", + "integrity": "sha512-hj1wFStD7B1YBeYmvY+lWXZ7ey73YGPcViMShYikqKT1GtstIKQAtfUI6yrzPjAy/O7pO0VLXGmUVWXQMaYgTQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.60.0.tgz", + "integrity": "sha512-SyaIPFoxmUPlNDq5EHkTbiKzmSEmq/gOYFI/3HHJ8iS/v1mbugVa7dXUzcJGQfoytp9DJFLhHH4U3/eTy2Bq4w==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.60.0.tgz", + "integrity": "sha512-RdcryEfzZr+lAr5kRm2ucN9aVlCCa2QNq4hXelZxb8GG0NJSazq44Z3PCCc8wISRuCVnGs0lQJVX5Vp6fKA+IA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.60.0.tgz", + "integrity": "sha512-PrsWNQ8BuE00O3Xsx3ALh2Df8fAj9+cvvX9AIA6o4KpATR98c9mud4XtDWVvsEuyia5U4tVSTKygawyJkjm60w==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@shikijs/core": { + "version": "3.23.0", + "resolved": "https://registry.npmjs.org/@shikijs/core/-/core-3.23.0.tgz", + "integrity": "sha512-NSWQz0riNb67xthdm5br6lAkvpDJRTgB36fxlo37ZzM2yq0PQFFzbd8psqC2XMPgCzo1fW6cVi18+ArJ44wqgA==", + "license": "MIT", + "dependencies": { + "@shikijs/types": "3.23.0", + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.4", + "hast-util-to-html": "^9.0.5" + } + }, + "node_modules/@shikijs/engine-javascript": { + "version": "3.23.0", + "resolved": "https://registry.npmjs.org/@shikijs/engine-javascript/-/engine-javascript-3.23.0.tgz", + "integrity": "sha512-aHt9eiGFobmWR5uqJUViySI1bHMqrAgamWE1TYSUoftkAeCCAiGawPMwM+VCadylQtF4V3VNOZ5LmfItH5f3yA==", + "license": "MIT", + "dependencies": { + "@shikijs/types": "3.23.0", + "@shikijs/vscode-textmate": "^10.0.2", + "oniguruma-to-es": "^4.3.4" + } + }, + "node_modules/@shikijs/engine-oniguruma": { + "version": "3.23.0", + "resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-3.23.0.tgz", + "integrity": "sha512-1nWINwKXxKKLqPibT5f4pAFLej9oZzQTsby8942OTlsJzOBZ0MWKiwzMsd+jhzu8YPCHAswGnnN1YtQfirL35g==", + "license": "MIT", + "dependencies": { + "@shikijs/types": "3.23.0", + "@shikijs/vscode-textmate": "^10.0.2" + } + }, + "node_modules/@shikijs/langs": { + "version": "3.23.0", + "resolved": "https://registry.npmjs.org/@shikijs/langs/-/langs-3.23.0.tgz", + "integrity": "sha512-2Ep4W3Re5aB1/62RSYQInK9mM3HsLeB91cHqznAJMuylqjzNVAVCMnNWRHFtcNHXsoNRayP9z1qj4Sq3nMqYXg==", + "license": "MIT", + "dependencies": { + "@shikijs/types": "3.23.0" + } + }, + "node_modules/@shikijs/themes": { + "version": "3.23.0", + "resolved": "https://registry.npmjs.org/@shikijs/themes/-/themes-3.23.0.tgz", + "integrity": "sha512-5qySYa1ZgAT18HR/ypENL9cUSGOeI2x+4IvYJu4JgVJdizn6kG4ia5Q1jDEOi7gTbN4RbuYtmHh0W3eccOrjMA==", + "license": "MIT", + "dependencies": { + "@shikijs/types": "3.23.0" + } + }, + "node_modules/@shikijs/transformers": { + "version": "3.23.0", + "resolved": "https://registry.npmjs.org/@shikijs/transformers/-/transformers-3.23.0.tgz", + "integrity": "sha512-F9msZVxdF+krQNSdQ4V+Ja5QemeAoTQ2jxt7nJCwhDsdF1JWS3KxIQXA3lQbyKwS3J61oHRUSv4jYWv3CkaKTQ==", + "license": "MIT", + "dependencies": { + "@shikijs/core": "3.23.0", + "@shikijs/types": "3.23.0" + } + }, + "node_modules/@shikijs/types": { + "version": "3.23.0", + "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-3.23.0.tgz", + "integrity": "sha512-3JZ5HXOZfYjsYSk0yPwBrkupyYSLpAE26Qc0HLghhZNGTZg/SKxXIIgoxOpmmeQP0RRSDJTk1/vPfw9tbw+jSQ==", + "license": "MIT", + "dependencies": { + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.4" + } + }, + "node_modules/@shikijs/vscode-textmate": { + "version": "10.0.2", + "resolved": "https://registry.npmjs.org/@shikijs/vscode-textmate/-/vscode-textmate-10.0.2.tgz", + "integrity": "sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==", + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "license": "MIT" + }, + "node_modules/@types/hast": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", + "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/linkify-it": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-5.0.0.tgz", + "integrity": "sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==", + "license": "MIT" + }, + "node_modules/@types/markdown-it": { + "version": "14.1.2", + "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-14.1.2.tgz", + "integrity": "sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==", + "license": "MIT", + "dependencies": { + "@types/linkify-it": "^5", + "@types/mdurl": "^2" + } + }, + "node_modules/@types/mdast": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", + "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/mdurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-2.0.0.tgz", + "integrity": "sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==", + "license": "MIT" + }, + "node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "license": "MIT" + }, + "node_modules/@types/web-bluetooth": { + "version": "0.0.21", + "resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.21.tgz", + "integrity": "sha512-oIQLCGWtcFZy2JW77j9k8nHzAOpqMHLQejDA48XXMWH6tjCQHz5RCFz1bzsmROyL6PUm+LLnUiI4BCn221inxA==", + "license": "MIT" + }, + "node_modules/@ungap/structured-clone": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", + "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", + "license": "ISC" + }, + "node_modules/@vitejs/plugin-vue": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-6.0.5.tgz", + "integrity": "sha512-bL3AxKuQySfk1iGcBsQnoRVexTPJq0Z/ixFVM8OhVJAP6ZXXXLtM7NFKWhLl30Kg7uTBqIaPXbh+nuQCuBDedg==", + "license": "MIT", + "dependencies": { + "@rolldown/pluginutils": "1.0.0-rc.2" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "peerDependencies": { + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0", + "vue": "^3.2.25" + } + }, + "node_modules/@vue/compiler-core": { + "version": "3.5.31", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.31.tgz", + "integrity": "sha512-k/ueL14aNIEy5Onf0OVzR8kiqF/WThgLdFhxwa4e/KF/0qe38IwIdofoSWBTvvxQOesaz6riAFAUaYjoF9fLLQ==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.2", + "@vue/shared": "3.5.31", + "entities": "^7.0.1", + "estree-walker": "^2.0.2", + "source-map-js": "^1.2.1" + } + }, + "node_modules/@vue/compiler-dom": { + "version": "3.5.31", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.31.tgz", + "integrity": "sha512-BMY/ozS/xxjYqRFL+tKdRpATJYDTTgWSo0+AJvJNg4ig+Hgb0dOsHPXvloHQ5hmlivUqw1Yt2pPIqp4e0v1GUw==", + "license": "MIT", + "dependencies": { + "@vue/compiler-core": "3.5.31", + "@vue/shared": "3.5.31" + } + }, + "node_modules/@vue/compiler-sfc": { + "version": "3.5.31", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.31.tgz", + "integrity": "sha512-M8wpPgR9UJ8MiRGjppvx9uWJfLV7A/T+/rL8s/y3QG3u0c2/YZgff3d6SuimKRIhcYnWg5fTfDMlz2E6seUW8Q==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.2", + "@vue/compiler-core": "3.5.31", + "@vue/compiler-dom": "3.5.31", + "@vue/compiler-ssr": "3.5.31", + "@vue/shared": "3.5.31", + "estree-walker": "^2.0.2", + "magic-string": "^0.30.21", + "postcss": "^8.5.8", + "source-map-js": "^1.2.1" + } + }, + "node_modules/@vue/compiler-ssr": { + "version": "3.5.31", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.31.tgz", + "integrity": "sha512-h0xIMxrt/LHOvJKMri+vdYT92BrK3HFLtDqq9Pr/lVVfE4IyKZKvWf0vJFW10Yr6nX02OR4MkJwI0c1HDa1hog==", + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.31", + "@vue/shared": "3.5.31" + } + }, + "node_modules/@vue/devtools-api": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-8.1.1.tgz", + "integrity": "sha512-bsDMJ07b3GN1puVwJb/fyFnj/U2imyswK5UQVLZwVl7O05jDrt6BHxeG5XffmOOdasOj/bOmIjxJvGPxU7pcqw==", + "license": "MIT", + "dependencies": { + "@vue/devtools-kit": "^8.1.1" + } + }, + "node_modules/@vue/devtools-kit": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/@vue/devtools-kit/-/devtools-kit-8.1.1.tgz", + "integrity": "sha512-gVBaBv++i+adg4JpH71k9ppl4soyR7Y2McEqO5YNgv0BI1kMZ7BDX5gnwkZ5COYgiCyhejZG+yGNrBAjj6Coqg==", + "license": "MIT", + "dependencies": { + "@vue/devtools-shared": "^8.1.1", + "birpc": "^2.6.1", + "hookable": "^5.5.3", + "perfect-debounce": "^2.0.0" + } + }, + "node_modules/@vue/devtools-shared": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/@vue/devtools-shared/-/devtools-shared-8.1.1.tgz", + "integrity": "sha512-+h4ttmJYl/txpxHKaoZcaKpC+pvckgLzIDiSQlaQ7kKthKh8KuwoLW2D8hPJEnqKzXOvu15UHEoGyngAXCz0EQ==", + "license": "MIT" + }, + "node_modules/@vue/reactivity": { + "version": "3.5.31", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.31.tgz", + "integrity": "sha512-DtKXxk9E/KuVvt8VxWu+6Luc9I9ETNcqR1T1oW1gf02nXaZ1kuAx58oVu7uX9XxJR0iJCro6fqBLw9oSBELo5g==", + "license": "MIT", + "dependencies": { + "@vue/shared": "3.5.31" + } + }, + "node_modules/@vue/runtime-core": { + "version": "3.5.31", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.31.tgz", + "integrity": "sha512-AZPmIHXEAyhpkmN7aWlqjSfYynmkWlluDNPHMCZKFHH+lLtxP/30UJmoVhXmbDoP1Ng0jG0fyY2zCj1PnSSA6Q==", + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.31", + "@vue/shared": "3.5.31" + } + }, + "node_modules/@vue/runtime-dom": { + "version": "3.5.31", + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.31.tgz", + "integrity": "sha512-xQJsNRmGPeDCJq/u813tyonNgWBFjzfVkBwDREdEWndBnGdHLHgkwNBQxLtg4zDrzKTEcnikUy1UUNecb3lJ6g==", + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.31", + "@vue/runtime-core": "3.5.31", + "@vue/shared": "3.5.31", + "csstype": "^3.2.3" + } + }, + "node_modules/@vue/server-renderer": { + "version": "3.5.31", + "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.31.tgz", + "integrity": "sha512-GJuwRvMcdZX/CriUnyIIOGkx3rMV3H6sOu0JhdKbduaeCji6zb60iOGMY7tFoN24NfsUYoFBhshZtGxGpxO4iA==", + "license": "MIT", + "dependencies": { + "@vue/compiler-ssr": "3.5.31", + "@vue/shared": "3.5.31" + }, + "peerDependencies": { + "vue": "3.5.31" + } + }, + "node_modules/@vue/shared": { + "version": "3.5.31", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.31.tgz", + "integrity": "sha512-nBxuiuS9Lj5bPkPbWogPUnjxxWpkRniX7e5UBQDWl6Fsf4roq9wwV+cR7ezQ4zXswNvPIlsdj1slcLB7XCsRAw==", + "license": "MIT" + }, + "node_modules/@vueuse/core": { + "version": "14.2.1", + "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-14.2.1.tgz", + "integrity": "sha512-3vwDzV+GDUNpdegRY6kzpLm4Igptq+GA0QkJ3W61Iv27YWwW/ufSlOfgQIpN6FZRMG0mkaz4gglJRtq5SeJyIQ==", + "license": "MIT", + "dependencies": { + "@types/web-bluetooth": "^0.0.21", + "@vueuse/metadata": "14.2.1", + "@vueuse/shared": "14.2.1" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "vue": "^3.5.0" + } + }, + "node_modules/@vueuse/integrations": { + "version": "14.2.1", + "resolved": "https://registry.npmjs.org/@vueuse/integrations/-/integrations-14.2.1.tgz", + "integrity": "sha512-2LIUpBi/67PoXJGqSDQUF0pgQWpNHh7beiA+KG2AbybcNm+pTGWT6oPGlBgUoDWmYwfeQqM/uzOHqcILpKL7nA==", + "license": "MIT", + "dependencies": { + "@vueuse/core": "14.2.1", + "@vueuse/shared": "14.2.1" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "async-validator": "^4", + "axios": "^1", + "change-case": "^5", + "drauu": "^0.4", + "focus-trap": "^7 || ^8", + "fuse.js": "^7", + "idb-keyval": "^6", + "jwt-decode": "^4", + "nprogress": "^0.2", + "qrcode": "^1.5", + "sortablejs": "^1", + "universal-cookie": "^7 || ^8", + "vue": "^3.5.0" + }, + "peerDependenciesMeta": { + "async-validator": { + "optional": true + }, + "axios": { + "optional": true + }, + "change-case": { + "optional": true + }, + "drauu": { + "optional": true + }, + "focus-trap": { + "optional": true + }, + "fuse.js": { + "optional": true + }, + "idb-keyval": { + "optional": true + }, + "jwt-decode": { + "optional": true + }, + "nprogress": { + "optional": true + }, + "qrcode": { + "optional": true + }, + "sortablejs": { + "optional": true + }, + "universal-cookie": { + "optional": true + } + } + }, + "node_modules/@vueuse/metadata": { + "version": "14.2.1", + "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-14.2.1.tgz", + "integrity": "sha512-1ButlVtj5Sb/HDtIy1HFr1VqCP4G6Ypqt5MAo0lCgjokrk2mvQKsK2uuy0vqu/Ks+sHfuHo0B9Y9jn9xKdjZsw==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@vueuse/shared": { + "version": "14.2.1", + "resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-14.2.1.tgz", + "integrity": "sha512-shTJncjV9JTI4oVNyF1FQonetYAiTBd+Qj7cY89SWbXSkx7gyhrgtEdF2ZAVWS1S3SHlaROO6F2IesJxQEkZBw==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "vue": "^3.5.0" + } + }, + "node_modules/birpc": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/birpc/-/birpc-2.9.0.tgz", + "integrity": "sha512-KrayHS5pBi69Xi9JmvoqrIgYGDkD6mcSe/i6YKi3w5kekCLzrX4+nawcXqrj2tIp50Kw/mT/s3p+GVK0A0sKxw==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/ccount": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", + "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-html4": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz", + "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-legacy": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", + "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/comma-separated-tokens": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", + "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "license": "MIT" + }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/devlop": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz", + "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==", + "license": "MIT", + "dependencies": { + "dequal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/entities": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz", + "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/esbuild": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.4.tgz", + "integrity": "sha512-Rq4vbHnYkK5fws5NF7MYTU68FPRE1ajX7heQ/8QXXWqNgqqJ/GkmmyxIzUnf2Sr/bakf8l54716CcMGHYhMrrQ==", + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.4", + "@esbuild/android-arm": "0.27.4", + "@esbuild/android-arm64": "0.27.4", + "@esbuild/android-x64": "0.27.4", + "@esbuild/darwin-arm64": "0.27.4", + "@esbuild/darwin-x64": "0.27.4", + "@esbuild/freebsd-arm64": "0.27.4", + "@esbuild/freebsd-x64": "0.27.4", + "@esbuild/linux-arm": "0.27.4", + "@esbuild/linux-arm64": "0.27.4", + "@esbuild/linux-ia32": "0.27.4", + "@esbuild/linux-loong64": "0.27.4", + "@esbuild/linux-mips64el": "0.27.4", + "@esbuild/linux-ppc64": "0.27.4", + "@esbuild/linux-riscv64": "0.27.4", + "@esbuild/linux-s390x": "0.27.4", + "@esbuild/linux-x64": "0.27.4", + "@esbuild/netbsd-arm64": "0.27.4", + "@esbuild/netbsd-x64": "0.27.4", + "@esbuild/openbsd-arm64": "0.27.4", + "@esbuild/openbsd-x64": "0.27.4", + "@esbuild/openharmony-arm64": "0.27.4", + "@esbuild/sunos-x64": "0.27.4", + "@esbuild/win32-arm64": "0.27.4", + "@esbuild/win32-ia32": "0.27.4", + "@esbuild/win32-x64": "0.27.4" + } + }, + "node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "license": "MIT" + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/focus-trap": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/focus-trap/-/focus-trap-8.0.1.tgz", + "integrity": "sha512-9ptSG6z51YQOstI/oN4XuVGP/03u2nh0g//qz7L6zX0i6PZiPnkcf3GenXq7N2hZnASXaMxTPpbKwdI+PFvxlw==", + "license": "MIT", + "dependencies": { + "tabbable": "^6.4.0" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/hast-util-to-html": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/hast-util-to-html/-/hast-util-to-html-9.0.5.tgz", + "integrity": "sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "ccount": "^2.0.0", + "comma-separated-tokens": "^2.0.0", + "hast-util-whitespace": "^3.0.0", + "html-void-elements": "^3.0.0", + "mdast-util-to-hast": "^13.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0", + "stringify-entities": "^4.0.0", + "zwitch": "^2.0.4" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-whitespace": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz", + "integrity": "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hookable": { + "version": "5.5.3", + "resolved": "https://registry.npmjs.org/hookable/-/hookable-5.5.3.tgz", + "integrity": "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==", + "license": "MIT" + }, + "node_modules/html-void-elements": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-3.0.0.tgz", + "integrity": "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/mark.js": { + "version": "8.11.1", + "resolved": "https://registry.npmjs.org/mark.js/-/mark.js-8.11.1.tgz", + "integrity": "sha512-1I+1qpDt4idfgLQG+BNWmrqku+7/2bi5nLf4YwF8y8zXvmfiTBY3PV3ZibfrjBueCByROpuBjLLFCajqkgYoLQ==", + "license": "MIT" + }, + "node_modules/mdast-util-to-hast": { + "version": "13.2.1", + "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.1.tgz", + "integrity": "sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@ungap/structured-clone": "^1.0.0", + "devlop": "^1.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "trim-lines": "^3.0.0", + "unist-util-position": "^5.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-encode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz", + "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-sanitize-uri": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz", + "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-types": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.2.tgz", + "integrity": "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/minisearch": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/minisearch/-/minisearch-7.2.0.tgz", + "integrity": "sha512-dqT2XBYUOZOiC5t2HRnwADjhNS2cecp9u+TJRiJ1Qp/f5qjkeT5APcGPjHw+bz89Ms8Jp+cG4AlE+QZ/QnDglg==", + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/oniguruma-parser": { + "version": "0.12.1", + "resolved": "https://registry.npmjs.org/oniguruma-parser/-/oniguruma-parser-0.12.1.tgz", + "integrity": "sha512-8Unqkvk1RYc6yq2WBYRj4hdnsAxVze8i7iPfQr8e4uSP3tRv0rpZcbGUDvxfQQcdwHt/e9PrMvGCsa8OqG9X3w==", + "license": "MIT" + }, + "node_modules/oniguruma-to-es": { + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/oniguruma-to-es/-/oniguruma-to-es-4.3.5.tgz", + "integrity": "sha512-Zjygswjpsewa0NLTsiizVuMQZbp0MDyM6lIt66OxsF21npUDlzpHi1Mgb/qhQdkb+dWFTzJmFbEWdvZgRho8eQ==", + "license": "MIT", + "dependencies": { + "oniguruma-parser": "^0.12.1", + "regex": "^6.1.0", + "regex-recursion": "^6.0.2" + } + }, + "node_modules/perfect-debounce": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-2.1.0.tgz", + "integrity": "sha512-LjgdTytVFXeUgtHZr9WYViYSM/g8MkcTPYDlPa3cDqMirHjKiSZPYd6DoL7pK8AJQr+uWkQvCjHNdiMqsrJs+g==", + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.8", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.8.tgz", + "integrity": "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/property-information": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-7.1.0.tgz", + "integrity": "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/regex/-/regex-6.1.0.tgz", + "integrity": "sha512-6VwtthbV4o/7+OaAF9I5L5V3llLEsoPyq9P1JVXkedTP33c7MfCG0/5NOPcSJn0TzXcG9YUrR0gQSWioew3LDg==", + "license": "MIT", + "dependencies": { + "regex-utilities": "^2.3.0" + } + }, + "node_modules/regex-recursion": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/regex-recursion/-/regex-recursion-6.0.2.tgz", + "integrity": "sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg==", + "license": "MIT", + "dependencies": { + "regex-utilities": "^2.3.0" + } + }, + "node_modules/regex-utilities": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/regex-utilities/-/regex-utilities-2.3.0.tgz", + "integrity": "sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng==", + "license": "MIT" + }, + "node_modules/rollup": { + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.60.0.tgz", + "integrity": "sha512-yqjxruMGBQJ2gG4HtjZtAfXArHomazDHoFwFFmZZl0r7Pdo7qCIXKqKHZc8yeoMgzJJ+pO6pEEHa+V7uzWlrAQ==", + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.60.0", + "@rollup/rollup-android-arm64": "4.60.0", + "@rollup/rollup-darwin-arm64": "4.60.0", + "@rollup/rollup-darwin-x64": "4.60.0", + "@rollup/rollup-freebsd-arm64": "4.60.0", + "@rollup/rollup-freebsd-x64": "4.60.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.60.0", + "@rollup/rollup-linux-arm-musleabihf": "4.60.0", + "@rollup/rollup-linux-arm64-gnu": "4.60.0", + "@rollup/rollup-linux-arm64-musl": "4.60.0", + "@rollup/rollup-linux-loong64-gnu": "4.60.0", + "@rollup/rollup-linux-loong64-musl": "4.60.0", + "@rollup/rollup-linux-ppc64-gnu": "4.60.0", + "@rollup/rollup-linux-ppc64-musl": "4.60.0", + "@rollup/rollup-linux-riscv64-gnu": "4.60.0", + "@rollup/rollup-linux-riscv64-musl": "4.60.0", + "@rollup/rollup-linux-s390x-gnu": "4.60.0", + "@rollup/rollup-linux-x64-gnu": "4.60.0", + "@rollup/rollup-linux-x64-musl": "4.60.0", + "@rollup/rollup-openbsd-x64": "4.60.0", + "@rollup/rollup-openharmony-arm64": "4.60.0", + "@rollup/rollup-win32-arm64-msvc": "4.60.0", + "@rollup/rollup-win32-ia32-msvc": "4.60.0", + "@rollup/rollup-win32-x64-gnu": "4.60.0", + "@rollup/rollup-win32-x64-msvc": "4.60.0", + "fsevents": "~2.3.2" + } + }, + "node_modules/shiki": { + "version": "3.23.0", + "resolved": "https://registry.npmjs.org/shiki/-/shiki-3.23.0.tgz", + "integrity": "sha512-55Dj73uq9ZXL5zyeRPzHQsK7Nbyt6Y10k5s7OjuFZGMhpp4r/rsLBH0o/0fstIzX1Lep9VxefWljK/SKCzygIA==", + "license": "MIT", + "dependencies": { + "@shikijs/core": "3.23.0", + "@shikijs/engine-javascript": "3.23.0", + "@shikijs/engine-oniguruma": "3.23.0", + "@shikijs/langs": "3.23.0", + "@shikijs/themes": "3.23.0", + "@shikijs/types": "3.23.0", + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.4" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/space-separated-tokens": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz", + "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/stringify-entities": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz", + "integrity": "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==", + "license": "MIT", + "dependencies": { + "character-entities-html4": "^2.0.0", + "character-entities-legacy": "^3.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/tabbable": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.4.0.tgz", + "integrity": "sha512-05PUHKSNE8ou2dwIxTngl4EzcnsCDZGJ/iCLtDflR/SHB/ny14rXc+qU5P4mG9JkusiV7EivzY9Mhm55AzAvCg==", + "license": "MIT" + }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/trim-lines": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", + "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/unist-util-is": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.1.tgz", + "integrity": "sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-position": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-5.0.0.tgz", + "integrity": "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-stringify-position": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", + "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.1.0.tgz", + "integrity": "sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit-parents": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.2.tgz", + "integrity": "sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz", + "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-message": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.3.tgz", + "integrity": "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vite": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz", + "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==", + "license": "MIT", + "dependencies": { + "esbuild": "^0.27.0", + "fdir": "^6.5.0", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rollup": "^4.43.0", + "tinyglobby": "^0.2.15" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "lightningcss": "^1.21.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vitepress": { + "version": "2.0.0-alpha.17", + "resolved": "https://registry.npmjs.org/vitepress/-/vitepress-2.0.0-alpha.17.tgz", + "integrity": "sha512-Z3VPUpwk/bHYqt1uMVOOK1/4xFiWQov1GNc2FvMdz6kvje4JRXEOngVI9C+bi5jeedMSHiA4dwKkff1NCvbZ9Q==", + "license": "MIT", + "dependencies": { + "@docsearch/css": "^4.5.3", + "@docsearch/js": "^4.5.3", + "@docsearch/sidepanel-js": "^4.5.3", + "@iconify-json/simple-icons": "^1.2.69", + "@shikijs/core": "^3.22.0", + "@shikijs/transformers": "^3.22.0", + "@shikijs/types": "^3.22.0", + "@types/markdown-it": "^14.1.2", + "@vitejs/plugin-vue": "^6.0.4", + "@vue/devtools-api": "^8.0.5", + "@vue/shared": "^3.5.27", + "@vueuse/core": "^14.2.0", + "@vueuse/integrations": "^14.2.0", + "focus-trap": "^8.0.0", + "mark.js": "8.11.1", + "minisearch": "^7.2.0", + "shiki": "^3.22.0", + "vite": "^7.3.1", + "vue": "^3.5.27" + }, + "bin": { + "vitepress": "bin/vitepress.js" + }, + "peerDependencies": { + "markdown-it-mathjax3": "^4", + "oxc-minify": "*", + "postcss": "^8" + }, + "peerDependenciesMeta": { + "markdown-it-mathjax3": { + "optional": true + }, + "oxc-minify": { + "optional": true + }, + "postcss": { + "optional": true + } + } + }, + "node_modules/vue": { + "version": "3.5.31", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.31.tgz", + "integrity": "sha512-iV/sU9SzOlmA/0tygSmjkEN6Jbs3nPoIPFhCMLD2STrjgOU8DX7ZtzMhg4ahVwf5Rp9KoFzcXeB1ZrVbLBp5/Q==", + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.31", + "@vue/compiler-sfc": "3.5.31", + "@vue/runtime-dom": "3.5.31", + "@vue/server-renderer": "3.5.31", + "@vue/shared": "3.5.31" + }, + "peerDependencies": { + "typescript": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/zwitch": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", + "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + } + } +} diff --git a/docs/package.json b/docs/package.json new file mode 100644 index 00000000..fb3f3a14 --- /dev/null +++ b/docs/package.json @@ -0,0 +1,19 @@ +{ + "name": "docs", + "version": "1.0.0", + "description": "", + "main": "config.js", + "scripts": { + "docs:dev": "vitepress dev", + "docs:build": "vitepress build", + "docs:preview": "vitepress preview" + }, + "keywords": [], + "author": "", + "license": "ISC", + "type": "commonjs", + "dependencies": { + "@cakephp/docs-skeleton": "github:cakephp/docs-skeleton#node-package", + "vitepress": "^2.0.0-alpha.16" + } +}