Merge pull request #11993 from appwrite/feat-public-oauth2-endpoints

Feat: Public project OAuth2 configuration API
This commit is contained in:
Matej Bačo
2026-04-28 12:41:50 +02:00
committed by GitHub
122 changed files with 10933 additions and 105 deletions
+2
View File
@@ -146,3 +146,5 @@ _APP_STATS_USAGE_DUAL_WRITING_DBS=database_db_main
_APP_TRUSTED_HEADERS=x-forwarded-for
_APP_POOL_ADAPTER=stack
_APP_WORKER_SCREENSHOTS_ROUTER=http://appwrite
_TESTS_OAUTH2_GITHUB_CLIENT_ID=
_TESTS_OAUTH2_GITHUB_CLIENT_SECRET=
+5 -1
View File
@@ -456,8 +456,10 @@ jobs:
name: ${{ env.IMAGE }}
path: /tmp
- name: Set database environment
- name: Set environment
run: |
echo "_APP_OPTIONS_ROUTER_PROTECTION=enabled" >> $GITHUB_ENV
if [ "${{ matrix.database }}" = "MariaDB" ]; then
echo "COMPOSE_PROFILES=mariadb" >> $GITHUB_ENV
echo "_APP_DB_ADAPTER=mariadb" >> $GITHUB_ENV
@@ -526,6 +528,8 @@ jobs:
docker compose exec -T \
-e _APP_E2E_RESPONSE_FORMAT="${{ github.event.inputs.response_format }}" \
-e _TESTS_OAUTH2_GITHUB_CLIENT_ID="${{ secrets.TESTS_OAUTH2_GITHUB_CLIENT_ID }}" \
-e _TESTS_OAUTH2_GITHUB_CLIENT_SECRET="${{ secrets.TESTS_OAUTH2_GITHUB_CLIENT_SECRET }}" \
appwrite vendor/bin/paratest --processes "$PARATEST_PROCESSES" $FUNCTIONAL_FLAG "$SERVICE_PATH" --exclude-group abuseEnabled --exclude-group screenshots --log-junit tests/e2e/Services/${{ matrix.service }}/junit.xml
- name: Failure Logs
+33
View File
@@ -167,6 +167,17 @@ return [
'mock' => false,
'class' => 'Appwrite\\Auth\\OAuth2\\Figma',
],
'fusionauth' => [
'name' => 'FusionAuth',
'developers' => 'https://fusionauth.io/docs/',
'icon' => 'icon-fusionauth',
'enabled' => true,
'sandbox' => false,
'form' => 'fusionauth.phtml',
'beta' => false,
'mock' => false,
'class' => 'Appwrite\\Auth\\OAuth2\\FusionAuth',
],
'github' => [
'name' => 'GitHub',
'developers' => 'https://developer.github.com/',
@@ -200,6 +211,28 @@ return [
'mock' => false,
'class' => 'Appwrite\\Auth\\OAuth2\\Google',
],
'keycloak' => [
'name' => 'Keycloak',
'developers' => 'https://www.keycloak.org/documentation',
'icon' => 'icon-keycloak',
'enabled' => true,
'sandbox' => false,
'form' => 'keycloak.phtml',
'beta' => false,
'mock' => false,
'class' => 'Appwrite\\Auth\\OAuth2\\Keycloak',
],
'kick' => [
'name' => 'Kick',
'developers' => 'https://docs.kick.com/',
'icon' => 'icon-kick',
'enabled' => true,
'sandbox' => false,
'form' => false,
'beta' => false,
'mock' => false,
'class' => 'Appwrite\\Auth\\OAuth2\\Kick',
],
'linkedin' => [
'name' => 'LinkedIn',
'developers' => 'https://developer.linkedin.com/',
+2
View File
@@ -55,6 +55,8 @@ $admins = [
'tables.write',
'platforms.read',
'platforms.write',
'oauth2.read',
'oauth2.write',
'mocks.read',
'mocks.write',
'policies.read',
+8
View File
@@ -228,4 +228,12 @@ return [ // List of publicly visible scopes
"description" =>
"Access to create, update, and delete project\'s templates",
],
"oauth2.read" => [
"description" =>
"Access to read project\'s OAuth2 configuration",
],
"oauth2.write" => [
"description" =>
"Access to update project\'s OAuth2 configuration",
],
];
+1 -13
View File
@@ -58,23 +58,11 @@ Http::get('/v1/projects/:projectId')
$response->dynamic($project, Response::MODEL_PROJECT);
});
// Backwards compatibility
Http::patch('/v1/projects/:projectId/oauth2')
->desc('Update project OAuth2')
->groups(['api', 'projects'])
->label('scope', 'projects.write')
->label('sdk', new Method(
namespace: 'projects',
group: 'auth',
name: 'updateOAuth2',
description: '/docs/references/projects/update-oauth2.md',
auth: [AuthType::ADMIN],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
model: Response::MODEL_PROJECT,
)
]
))
->param('projectId', '', fn (Database $dbForPlatform) => new UID($dbForPlatform->getAdapter()->getMaxUIDLength()), 'Project unique ID.', false, ['dbForPlatform'])
->param('provider', '', new WhiteList(\array_keys(Config::getParam('oAuthProviders')), true), 'Provider Name')
->param('appId', null, new Nullable(new Text(256)), 'Provider app ID. Max length: 256 chars.', true)
+1 -1
View File
@@ -120,7 +120,7 @@ function router(Http $utopia, Database $dbForPlatform, callable $getProjectDB, S
}
}
if (!in_array($host, $platformHostnames)) {
if (!in_array($host, $platformHostnames) && System::getEnv('_APP_OPTIONS_ROUTER_PROTECTION', 'enabled') === 'enabled') {
throw new AppwriteException(AppwriteException::GENERAL_ACCESS_FORBIDDEN, 'Router protection does not allow accessing Appwrite over this domain. Please add it as custom domain to your project or disable _APP_OPTIONS_ROUTER_PROTECTION environment variable.', view: $errorView);
}
+88
View File
@@ -56,6 +56,9 @@ use Appwrite\Utopia\Response\Model\ColumnString;
use Appwrite\Utopia\Response\Model\ColumnText;
use Appwrite\Utopia\Response\Model\ColumnURL;
use Appwrite\Utopia\Response\Model\ColumnVarchar;
use Appwrite\Utopia\Response\Model\ConsoleOAuth2Provider;
use Appwrite\Utopia\Response\Model\ConsoleOAuth2ProviderList;
use Appwrite\Utopia\Response\Model\ConsoleOAuth2ProviderParameter;
use Appwrite\Utopia\Response\Model\ConsoleVariables;
use Appwrite\Utopia\Response\Model\Continent;
use Appwrite\Utopia\Response\Model\Country;
@@ -105,6 +108,47 @@ use Appwrite\Utopia\Response\Model\MigrationReport;
use Appwrite\Utopia\Response\Model\Mock;
use Appwrite\Utopia\Response\Model\MockNumber;
use Appwrite\Utopia\Response\Model\None;
use Appwrite\Utopia\Response\Model\OAuth2Amazon;
use Appwrite\Utopia\Response\Model\OAuth2Apple;
use Appwrite\Utopia\Response\Model\OAuth2Auth0;
use Appwrite\Utopia\Response\Model\OAuth2Authentik;
use Appwrite\Utopia\Response\Model\OAuth2Autodesk;
use Appwrite\Utopia\Response\Model\OAuth2Bitbucket;
use Appwrite\Utopia\Response\Model\OAuth2Bitly;
use Appwrite\Utopia\Response\Model\OAuth2Box;
use Appwrite\Utopia\Response\Model\OAuth2Dailymotion;
use Appwrite\Utopia\Response\Model\OAuth2Discord;
use Appwrite\Utopia\Response\Model\OAuth2Disqus;
use Appwrite\Utopia\Response\Model\OAuth2Dropbox;
use Appwrite\Utopia\Response\Model\OAuth2Etsy;
use Appwrite\Utopia\Response\Model\OAuth2Facebook;
use Appwrite\Utopia\Response\Model\OAuth2Figma;
use Appwrite\Utopia\Response\Model\OAuth2FusionAuth;
use Appwrite\Utopia\Response\Model\OAuth2GitHub;
use Appwrite\Utopia\Response\Model\OAuth2Gitlab;
use Appwrite\Utopia\Response\Model\OAuth2Google;
use Appwrite\Utopia\Response\Model\OAuth2Keycloak;
use Appwrite\Utopia\Response\Model\OAuth2Kick;
use Appwrite\Utopia\Response\Model\OAuth2Linkedin;
use Appwrite\Utopia\Response\Model\OAuth2Microsoft;
use Appwrite\Utopia\Response\Model\OAuth2Notion;
use Appwrite\Utopia\Response\Model\OAuth2Oidc;
use Appwrite\Utopia\Response\Model\OAuth2Okta;
use Appwrite\Utopia\Response\Model\OAuth2Paypal;
use Appwrite\Utopia\Response\Model\OAuth2Podio;
use Appwrite\Utopia\Response\Model\OAuth2ProviderList;
use Appwrite\Utopia\Response\Model\OAuth2Salesforce;
use Appwrite\Utopia\Response\Model\OAuth2Slack;
use Appwrite\Utopia\Response\Model\OAuth2Spotify;
use Appwrite\Utopia\Response\Model\OAuth2Stripe;
use Appwrite\Utopia\Response\Model\OAuth2Tradeshift;
use Appwrite\Utopia\Response\Model\OAuth2Twitch;
use Appwrite\Utopia\Response\Model\OAuth2WordPress;
use Appwrite\Utopia\Response\Model\OAuth2X;
use Appwrite\Utopia\Response\Model\OAuth2Yahoo;
use Appwrite\Utopia\Response\Model\OAuth2Yandex;
use Appwrite\Utopia\Response\Model\OAuth2Zoho;
use Appwrite\Utopia\Response\Model\OAuth2Zoom;
use Appwrite\Utopia\Response\Model\Phone;
use Appwrite\Utopia\Response\Model\PlatformAndroid;
use Appwrite\Utopia\Response\Model\PlatformApple;
@@ -350,6 +394,47 @@ Response::setModel(new Webhook());
Response::setModel(new Key());
Response::setModel(new DevKey());
Response::setModel(new MockNumber());
Response::setModel(new OAuth2GitHub());
Response::setModel(new OAuth2Discord());
Response::setModel(new OAuth2Figma());
Response::setModel(new OAuth2Dropbox());
Response::setModel(new OAuth2Dailymotion());
Response::setModel(new OAuth2Bitbucket());
Response::setModel(new OAuth2Bitly());
Response::setModel(new OAuth2Box());
Response::setModel(new OAuth2Autodesk());
Response::setModel(new OAuth2Google());
Response::setModel(new OAuth2Zoom());
Response::setModel(new OAuth2Zoho());
Response::setModel(new OAuth2Yandex());
Response::setModel(new OAuth2X());
Response::setModel(new OAuth2WordPress());
Response::setModel(new OAuth2Twitch());
Response::setModel(new OAuth2Stripe());
Response::setModel(new OAuth2Spotify());
Response::setModel(new OAuth2Slack());
Response::setModel(new OAuth2Podio());
Response::setModel(new OAuth2Notion());
Response::setModel(new OAuth2Salesforce());
Response::setModel(new OAuth2Yahoo());
Response::setModel(new OAuth2Linkedin());
Response::setModel(new OAuth2Disqus());
Response::setModel(new OAuth2Amazon());
Response::setModel(new OAuth2Etsy());
Response::setModel(new OAuth2Facebook());
Response::setModel(new OAuth2Tradeshift());
Response::setModel(new OAuth2Paypal());
Response::setModel(new OAuth2Gitlab());
Response::setModel(new OAuth2Authentik());
Response::setModel(new OAuth2Auth0());
Response::setModel(new OAuth2FusionAuth());
Response::setModel(new OAuth2Keycloak());
Response::setModel(new OAuth2Oidc());
Response::setModel(new OAuth2Okta());
Response::setModel(new OAuth2Kick());
Response::setModel(new OAuth2Apple());
Response::setModel(new OAuth2Microsoft());
Response::setModel(new OAuth2ProviderList());
Response::setModel(new PolicyPasswordDictionary());
Response::setModel(new PolicyPasswordHistory());
Response::setModel(new PolicyPasswordPersonalData());
@@ -398,6 +483,9 @@ Response::setModel(new Rule());
Response::setModel(new Schedule());
Response::setModel(new TemplateEmail());
Response::setModel(new ConsoleVariables());
Response::setModel(new ConsoleOAuth2ProviderParameter());
Response::setModel(new ConsoleOAuth2Provider());
Response::setModel(new ConsoleOAuth2ProviderList());
Response::setModel(new MFAChallenge());
Response::setModel(new MFARecoveryCodes());
Response::setModel(new MFAType());
+1
View File
@@ -69,6 +69,7 @@
"utopia-php/dsn": "0.2.1",
"utopia-php/http": "0.34.*",
"utopia-php/fetch": "0.5.*",
"utopia-php/validators": "0.2.*",
"utopia-php/image": "0.8.*",
"utopia-php/locale": "0.8.*",
"utopia-php/logger": "0.6.*",
Generated
+88 -86
View File
@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "c5ae97637fd0ec0a950044d1c33677ea",
"content-hash": "805802552f7482eaeae4bdaa505ae982",
"packages": [
{
"name": "adhocore/jwt",
@@ -1996,16 +1996,16 @@
},
{
"name": "phpseclib/phpseclib",
"version": "3.0.51",
"version": "3.0.52",
"source": {
"type": "git",
"url": "https://github.com/phpseclib/phpseclib.git",
"reference": "d59c94077f9c9915abb51ddb52ce85188ece1748"
"reference": "2adaefc83df2ec548558307690f376dd7d4f4fce"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/d59c94077f9c9915abb51ddb52ce85188ece1748",
"reference": "d59c94077f9c9915abb51ddb52ce85188ece1748",
"url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/2adaefc83df2ec548558307690f376dd7d4f4fce",
"reference": "2adaefc83df2ec548558307690f376dd7d4f4fce",
"shasum": ""
},
"require": {
@@ -2086,7 +2086,7 @@
],
"support": {
"issues": "https://github.com/phpseclib/phpseclib/issues",
"source": "https://github.com/phpseclib/phpseclib/tree/3.0.51"
"source": "https://github.com/phpseclib/phpseclib/tree/3.0.52"
},
"funding": [
{
@@ -2102,7 +2102,7 @@
"type": "tidelift"
}
],
"time": "2026-04-10T01:33:53+00:00"
"time": "2026-04-27T07:02:15+00:00"
},
{
"name": "psr/clock",
@@ -2887,7 +2887,7 @@
},
{
"name": "symfony/polyfill-mbstring",
"version": "v1.36.0",
"version": "v1.37.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-mbstring.git",
@@ -2948,7 +2948,7 @@
"shim"
],
"support": {
"source": "https://github.com/symfony/polyfill-mbstring/tree/v1.36.0"
"source": "https://github.com/symfony/polyfill-mbstring/tree/v1.37.0"
},
"funding": [
{
@@ -2972,7 +2972,7 @@
},
{
"name": "symfony/polyfill-php82",
"version": "v1.36.0",
"version": "v1.37.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-php82.git",
@@ -3028,7 +3028,7 @@
"shim"
],
"support": {
"source": "https://github.com/symfony/polyfill-php82/tree/v1.36.0"
"source": "https://github.com/symfony/polyfill-php82/tree/v1.37.0"
},
"funding": [
{
@@ -3052,7 +3052,7 @@
},
{
"name": "symfony/polyfill-php83",
"version": "v1.36.0",
"version": "v1.37.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-php83.git",
@@ -3108,7 +3108,7 @@
"shim"
],
"support": {
"source": "https://github.com/symfony/polyfill-php83/tree/v1.36.0"
"source": "https://github.com/symfony/polyfill-php83/tree/v1.37.0"
},
"funding": [
{
@@ -3132,16 +3132,16 @@
},
{
"name": "symfony/polyfill-php85",
"version": "v1.36.0",
"version": "v1.37.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-php85.git",
"reference": "2c408a6bb0313e6001a83628dc5506100474254e"
"reference": "fcfa4973a9917cef23f2e38774da74a2b7d115ee"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-php85/zipball/2c408a6bb0313e6001a83628dc5506100474254e",
"reference": "2c408a6bb0313e6001a83628dc5506100474254e",
"url": "https://api.github.com/repos/symfony/polyfill-php85/zipball/fcfa4973a9917cef23f2e38774da74a2b7d115ee",
"reference": "fcfa4973a9917cef23f2e38774da74a2b7d115ee",
"shasum": ""
},
"require": {
@@ -3188,7 +3188,7 @@
"shim"
],
"support": {
"source": "https://github.com/symfony/polyfill-php85/tree/v1.36.0"
"source": "https://github.com/symfony/polyfill-php85/tree/v1.37.0"
},
"funding": [
{
@@ -3208,7 +3208,7 @@
"type": "tidelift"
}
],
"time": "2026-04-10T16:50:15+00:00"
"time": "2026-04-26T13:10:57+00:00"
},
{
"name": "symfony/service-contracts",
@@ -3658,16 +3658,16 @@
},
{
"name": "utopia-php/cli",
"version": "0.23.1",
"version": "0.23.2",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/cli.git",
"reference": "8d1955b8bc4dc631f45d7c7df689ed7b63f70621"
"reference": "145b91fef827853bcceaa3ab8ca2b1d6faaca2ab"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/utopia-php/cli/zipball/8d1955b8bc4dc631f45d7c7df689ed7b63f70621",
"reference": "8d1955b8bc4dc631f45d7c7df689ed7b63f70621",
"url": "https://api.github.com/repos/utopia-php/cli/zipball/145b91fef827853bcceaa3ab8ca2b1d6faaca2ab",
"reference": "145b91fef827853bcceaa3ab8ca2b1d6faaca2ab",
"shasum": ""
},
"require": {
@@ -3703,9 +3703,9 @@
],
"support": {
"issues": "https://github.com/utopia-php/cli/issues",
"source": "https://github.com/utopia-php/cli/tree/0.23.1"
"source": "https://github.com/utopia-php/cli/tree/0.23.2"
},
"time": "2026-04-05T15:27:35+00:00"
"time": "2026-04-27T09:19:04+00:00"
},
{
"name": "utopia-php/compression",
@@ -4271,21 +4271,20 @@
},
{
"name": "utopia-php/http",
"version": "0.34.21",
"version": "0.34.24",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/http.git",
"reference": "49a6bd3ea0d2966aa19cf707255d442675288a24"
"reference": "d1eced0627c5a9fceddf53992ed97d664b810d33"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/utopia-php/http/zipball/49a6bd3ea0d2966aa19cf707255d442675288a24",
"reference": "49a6bd3ea0d2966aa19cf707255d442675288a24",
"url": "https://api.github.com/repos/utopia-php/http/zipball/d1eced0627c5a9fceddf53992ed97d664b810d33",
"reference": "d1eced0627c5a9fceddf53992ed97d664b810d33",
"shasum": ""
},
"require": {
"ext-swoole": "*",
"php": ">=8.2",
"php": ">=8.3",
"utopia-php/compression": "0.1.*",
"utopia-php/di": "0.3.*",
"utopia-php/servers": "0.3.*",
@@ -4295,11 +4294,14 @@
"require-dev": {
"doctrine/instantiator": "^1.5",
"laravel/pint": "1.*",
"phpbench/phpbench": "^1.2",
"phpstan/phpstan": "1.*",
"phpunit/phpunit": "^9.5.25",
"phpstan/phpstan": "^2.1",
"phpunit/phpunit": "^12.0",
"rector/rector": "^2.4",
"swoole/ide-helper": "4.8.3"
},
"suggest": {
"ext-swoole": "Required to use the Swoole server adapter (\\Utopia\\Http\\Adapter\\Swoole\\Server)."
},
"type": "library",
"autoload": {
"psr-4": {
@@ -4319,9 +4321,9 @@
],
"support": {
"issues": "https://github.com/utopia-php/http/issues",
"source": "https://github.com/utopia-php/http/tree/0.34.21"
"source": "https://github.com/utopia-php/http/tree/0.34.24"
},
"time": "2026-04-19T19:44:04+00:00"
"time": "2026-04-24T12:16:53+00:00"
},
{
"name": "utopia-php/image",
@@ -4528,16 +4530,16 @@
},
{
"name": "utopia-php/migration",
"version": "1.9.1",
"version": "1.9.3",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/migration.git",
"reference": "7a86aeadf182b63a9f4ceba7e137588b31c5d2e2"
"reference": "111f6221d04578a6f721c23ac872002375f176ae"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/utopia-php/migration/zipball/7a86aeadf182b63a9f4ceba7e137588b31c5d2e2",
"reference": "7a86aeadf182b63a9f4ceba7e137588b31c5d2e2",
"url": "https://api.github.com/repos/utopia-php/migration/zipball/111f6221d04578a6f721c23ac872002375f176ae",
"reference": "111f6221d04578a6f721c23ac872002375f176ae",
"shasum": ""
},
"require": {
@@ -4577,22 +4579,22 @@
],
"support": {
"issues": "https://github.com/utopia-php/migration/issues",
"source": "https://github.com/utopia-php/migration/tree/1.9.1"
"source": "https://github.com/utopia-php/migration/tree/1.9.3"
},
"time": "2026-03-25T07:05:27+00:00"
"time": "2026-04-22T07:13:26+00:00"
},
{
"name": "utopia-php/mongo",
"version": "1.0.2",
"version": "1.1.0",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/mongo.git",
"reference": "677a21c53f7a1316c528b4b45b3fce886cee7223"
"reference": "73593682deee4696525a04e26524c1c1226e1530"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/utopia-php/mongo/zipball/677a21c53f7a1316c528b4b45b3fce886cee7223",
"reference": "677a21c53f7a1316c528b4b45b3fce886cee7223",
"url": "https://api.github.com/repos/utopia-php/mongo/zipball/73593682deee4696525a04e26524c1c1226e1530",
"reference": "73593682deee4696525a04e26524c1c1226e1530",
"shasum": ""
},
"require": {
@@ -4638,9 +4640,9 @@
],
"support": {
"issues": "https://github.com/utopia-php/mongo/issues",
"source": "https://github.com/utopia-php/mongo/tree/1.0.2"
"source": "https://github.com/utopia-php/mongo/tree/1.1.0"
},
"time": "2026-03-18T02:45:50+00:00"
"time": "2026-04-24T06:15:10+00:00"
},
{
"name": "utopia-php/platform",
@@ -5182,16 +5184,16 @@
},
{
"name": "utopia-php/validators",
"version": "0.2.0",
"version": "0.2.2",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/validators.git",
"reference": "30b6030a5b100fc1dff34506e5053759594b2a20"
"reference": "5d7d494e64457cd4eb67fdcfd9481f2c89796aa6"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/utopia-php/validators/zipball/30b6030a5b100fc1dff34506e5053759594b2a20",
"reference": "30b6030a5b100fc1dff34506e5053759594b2a20",
"url": "https://api.github.com/repos/utopia-php/validators/zipball/5d7d494e64457cd4eb67fdcfd9481f2c89796aa6",
"reference": "5d7d494e64457cd4eb67fdcfd9481f2c89796aa6",
"shasum": ""
},
"require": {
@@ -5221,9 +5223,9 @@
],
"support": {
"issues": "https://github.com/utopia-php/validators/issues",
"source": "https://github.com/utopia-php/validators/tree/0.2.0"
"source": "https://github.com/utopia-php/validators/tree/0.2.2"
},
"time": "2026-01-13T09:16:51+00:00"
"time": "2026-04-27T16:30:24+00:00"
},
{
"name": "utopia-php/vcs",
@@ -5464,16 +5466,16 @@
"packages-dev": [
{
"name": "appwrite/sdk-generator",
"version": "1.20",
"version": "1.24.0",
"source": {
"type": "git",
"url": "https://github.com/appwrite/sdk-generator.git",
"reference": "525f0630520c95100fcdfb63c9dac859c1d02588"
"reference": "6d4d26659bc7a1c347c1d4d8dae3b77b5562e0cb"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/appwrite/sdk-generator/zipball/525f0630520c95100fcdfb63c9dac859c1d02588",
"reference": "525f0630520c95100fcdfb63c9dac859c1d02588",
"url": "https://api.github.com/repos/appwrite/sdk-generator/zipball/6d4d26659bc7a1c347c1d4d8dae3b77b5562e0cb",
"reference": "6d4d26659bc7a1c347c1d4d8dae3b77b5562e0cb",
"shasum": ""
},
"require": {
@@ -5509,9 +5511,9 @@
"description": "Appwrite PHP library for generating API SDKs for multiple programming languages and platforms",
"support": {
"issues": "https://github.com/appwrite/sdk-generator/issues",
"source": "https://github.com/appwrite/sdk-generator/tree/1.20"
"source": "https://github.com/appwrite/sdk-generator/tree/1.24.0"
},
"time": "2026-04-20T05:45:00+00:00"
"time": "2026-04-24T12:50:05+00:00"
},
{
"name": "brianium/paratest",
@@ -5793,16 +5795,16 @@
},
{
"name": "laravel/pint",
"version": "v1.29.0",
"version": "v1.29.1",
"source": {
"type": "git",
"url": "https://github.com/laravel/pint.git",
"reference": "bdec963f53172c5e36330f3a400604c69bf02d39"
"reference": "0770e9b7fafd50d4586881d456d6eb41c9247a80"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/laravel/pint/zipball/bdec963f53172c5e36330f3a400604c69bf02d39",
"reference": "bdec963f53172c5e36330f3a400604c69bf02d39",
"url": "https://api.github.com/repos/laravel/pint/zipball/0770e9b7fafd50d4586881d456d6eb41c9247a80",
"reference": "0770e9b7fafd50d4586881d456d6eb41c9247a80",
"shasum": ""
},
"require": {
@@ -5813,14 +5815,14 @@
"php": "^8.2.0"
},
"require-dev": {
"friendsofphp/php-cs-fixer": "^3.94.2",
"illuminate/view": "^12.54.1",
"larastan/larastan": "^3.9.3",
"laravel-zero/framework": "^12.0.5",
"friendsofphp/php-cs-fixer": "^3.95.1",
"illuminate/view": "^12.56.0",
"larastan/larastan": "^3.9.6",
"laravel-zero/framework": "^12.1.0",
"mockery/mockery": "^1.6.12",
"nunomaduro/termwind": "^2.4.0",
"pestphp/pest": "^3.8.6",
"shipfastlabs/agent-detector": "^1.1.0"
"shipfastlabs/agent-detector": "^1.1.3"
},
"bin": [
"builds/pint"
@@ -5857,7 +5859,7 @@
"issues": "https://github.com/laravel/pint/issues",
"source": "https://github.com/laravel/pint"
},
"time": "2026-03-12T15:51:39+00:00"
"time": "2026-04-20T15:26:14+00:00"
},
{
"name": "matthiasmullie/minify",
@@ -6220,11 +6222,11 @@
},
{
"name": "phpstan/phpstan",
"version": "2.1.50",
"version": "2.1.51",
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/d452086fb4cf648c6b2d8cf3b639351f79e4f3e2",
"reference": "d452086fb4cf648c6b2d8cf3b639351f79e4f3e2",
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/dc3b523c45e714c70de2ac5113b958223b55dc59",
"reference": "dc3b523c45e714c70de2ac5113b958223b55dc59",
"shasum": ""
},
"require": {
@@ -6269,7 +6271,7 @@
"type": "github"
}
],
"time": "2026-04-17T13:10:32+00:00"
"time": "2026-04-21T18:22:01+00:00"
},
{
"name": "phpunit/php-code-coverage",
@@ -7779,7 +7781,7 @@
},
{
"name": "symfony/polyfill-ctype",
"version": "v1.36.0",
"version": "v1.37.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-ctype.git",
@@ -7838,7 +7840,7 @@
"portable"
],
"support": {
"source": "https://github.com/symfony/polyfill-ctype/tree/v1.36.0"
"source": "https://github.com/symfony/polyfill-ctype/tree/v1.37.0"
},
"funding": [
{
@@ -7862,16 +7864,16 @@
},
{
"name": "symfony/polyfill-intl-grapheme",
"version": "v1.36.0",
"version": "v1.37.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-intl-grapheme.git",
"reference": "ad1b7b9092976d6c948b8a187cec9faaea9ec1df"
"reference": "4864388bfbd3001ce88e234fab652acd91fdc57e"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/ad1b7b9092976d6c948b8a187cec9faaea9ec1df",
"reference": "ad1b7b9092976d6c948b8a187cec9faaea9ec1df",
"url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/4864388bfbd3001ce88e234fab652acd91fdc57e",
"reference": "4864388bfbd3001ce88e234fab652acd91fdc57e",
"shasum": ""
},
"require": {
@@ -7920,7 +7922,7 @@
"shim"
],
"support": {
"source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.36.0"
"source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.37.0"
},
"funding": [
{
@@ -7940,11 +7942,11 @@
"type": "tidelift"
}
],
"time": "2026-04-10T16:19:22+00:00"
"time": "2026-04-26T13:13:48+00:00"
},
{
"name": "symfony/polyfill-intl-normalizer",
"version": "v1.36.0",
"version": "v1.37.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-intl-normalizer.git",
@@ -8005,7 +8007,7 @@
"shim"
],
"support": {
"source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.36.0"
"source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.37.0"
},
"funding": [
{
@@ -8029,7 +8031,7 @@
},
{
"name": "symfony/polyfill-php81",
"version": "v1.36.0",
"version": "v1.37.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-php81.git",
@@ -8085,7 +8087,7 @@
"shim"
],
"support": {
"source": "https://github.com/symfony/polyfill-php81/tree/v1.36.0"
"source": "https://github.com/symfony/polyfill-php81/tree/v1.37.0"
},
"funding": [
{
+2
View File
@@ -247,6 +247,8 @@ services:
- _APP_CUSTOM_DOMAIN_DENY_LIST
- _APP_TRUSTED_HEADERS
- _APP_MIGRATION_HOST
- _TESTS_OAUTH2_GITHUB_CLIENT_ID
- _TESTS_OAUTH2_GITHUB_CLIENT_SECRET
extra_hosts:
- "host.docker.internal:host-gateway"
@@ -0,0 +1 @@
List all OAuth2 providers supported by the Appwrite server, along with the parameters required to configure each provider. The response excludes mock providers but includes sandbox providers.
+226
View File
@@ -0,0 +1,226 @@
<?php
namespace Appwrite\Auth\OAuth2;
use Appwrite\Auth\OAuth2;
// Reference Material
// https://fusionauth.io/docs/lifecycle/authenticate-users/oauth/endpoints
class FusionAuth extends OAuth2
{
/**
* @var array
*/
protected array $scopes = [
'openid',
'profile',
'email',
'offline_access'
];
/**
* @var array
*/
protected array $user = [];
/**
* @var array
*/
protected array $tokens = [];
/**
* @return string
*/
public function getName(): string
{
return 'fusionauth';
}
/**
* @return string
*/
public function getLoginURL(): string
{
return 'https://' . $this->getFusionAuthDomain() . '/oauth2/authorize?' . \http_build_query([
'client_id' => $this->appID,
'redirect_uri' => $this->callback,
'state' => \json_encode($this->state),
'scope' => \implode(' ', $this->getScopes()),
'response_type' => 'code'
]);
}
/**
* @param string $code
*
* @return array
*/
protected function getTokens(string $code): array
{
if (empty($this->tokens)) {
$headers = ['Content-Type: application/x-www-form-urlencoded'];
$this->tokens = \json_decode($this->request(
'POST',
'https://' . $this->getFusionAuthDomain() . '/oauth2/token',
$headers,
\http_build_query([
'code' => $code,
'client_id' => $this->appID,
'client_secret' => $this->getClientSecret(),
'redirect_uri' => $this->callback,
'scope' => \implode(' ', $this->getScopes()),
'grant_type' => 'authorization_code'
])
), true);
}
return $this->tokens;
}
/**
* @param string $refreshToken
*
* @return array
*/
public function refreshTokens(string $refreshToken): array
{
$headers = ['Content-Type: application/x-www-form-urlencoded'];
$this->tokens = \json_decode($this->request(
'POST',
'https://' . $this->getFusionAuthDomain() . '/oauth2/token',
$headers,
\http_build_query([
'refresh_token' => $refreshToken,
'client_id' => $this->appID,
'client_secret' => $this->getClientSecret(),
'grant_type' => 'refresh_token'
])
), true);
if (empty($this->tokens['refresh_token'])) {
$this->tokens['refresh_token'] = $refreshToken;
}
return $this->tokens;
}
/**
* @param string $accessToken
*
* @return string
*/
public function getUserID(string $accessToken): string
{
$user = $this->getUser($accessToken);
if (isset($user['sub'])) {
return $user['sub'];
}
return '';
}
/**
* @param string $accessToken
*
* @return string
*/
public function getUserEmail(string $accessToken): string
{
$user = $this->getUser($accessToken);
if (isset($user['email'])) {
return $user['email'];
}
return '';
}
/**
* Check if the User email is verified
*
* @param string $accessToken
*
* @return bool
*/
public function isEmailVerified(string $accessToken): bool
{
$user = $this->getUser($accessToken);
if ($user['email_verified'] ?? false) {
return true;
}
return false;
}
/**
* @param string $accessToken
*
* @return string
*/
public function getUserName(string $accessToken): string
{
$user = $this->getUser($accessToken);
if (isset($user['name'])) {
return $user['name'];
}
return '';
}
/**
* @param string $accessToken
*
* @return array
*/
protected function getUser(string $accessToken): array
{
if (empty($this->user)) {
$headers = ['Authorization: Bearer ' . \urlencode($accessToken)];
$user = $this->request('GET', 'https://' . $this->getFusionAuthDomain() . '/oauth2/userinfo', $headers);
$this->user = \json_decode($user, true);
}
return $this->user;
}
/**
* Extracts the Client Secret from the JSON stored in appSecret
*
* @return string
*/
protected function getClientSecret(): string
{
$secret = $this->getAppSecret();
return $secret['clientSecret'] ?? '';
}
/**
* Extracts the FusionAuth Domain from the JSON stored in appSecret
*
* @return string
*/
protected function getFusionAuthDomain(): string
{
$secret = $this->getAppSecret();
return $secret['fusionAuthDomain'] ?? '';
}
/**
* Decode the JSON stored in appSecret
*
* @return array
*/
protected function getAppSecret(): array
{
try {
$secret = \json_decode($this->appSecret, true, 512, JSON_THROW_ON_ERROR);
} catch (\Throwable $th) {
throw new \Exception('Invalid secret');
}
return $secret;
}
}
+31
View File
@@ -3,6 +3,7 @@
namespace Appwrite\Auth\OAuth2;
use Appwrite\Auth\OAuth2;
use Utopia\Fetch\Client as FetchClient;
class Github extends OAuth2
{
@@ -219,4 +220,34 @@ class Github extends OAuth2
$repository = \json_decode($repository, true);
return $repository;
}
public function verifyCredentials(): void
{
$client = new FetchClient();
$client->addHeader('Accept', 'application/json');
$response = $client->fetch(
url: 'https://github.com/login/oauth/access_token',
method: FetchClient::METHOD_POST,
query: [
'client_id' => $this->appID,
'client_secret' => $this->appSecret,
'code' => 'intentionally-invalid-code',
'redirect_uri' => 'intentionally-invalid-redirect',
]
);
$json = \json_decode($response->getBody(), true);
if (isset($json['error']) && $json['error'] === "Not Found") {
throw new \Exception('GitHub application with provided Client ID is does not exist.');
}
if (isset($json['error']) && $json['error'] === "incorrect_client_credentials") {
throw new \Exception('GitHub application with provided Client ID is valid, but the provided Client Secret is incorrect.');
}
// We still expect error, like redirect_uri_mismatch or bad_verification_code,
// but that indicates valid credentials
}
}
+249
View File
@@ -0,0 +1,249 @@
<?php
namespace Appwrite\Auth\OAuth2;
use Appwrite\Auth\OAuth2;
// Reference Material
// https://www.keycloak.org/docs/latest/securing_apps/#_oidc
class Keycloak extends OAuth2
{
/**
* @var array
*/
protected array $scopes = [
'openid',
'profile',
'email',
'offline_access'
];
/**
* @var array
*/
protected array $user = [];
/**
* @var array
*/
protected array $tokens = [];
/**
* @return string
*/
public function getName(): string
{
return 'keycloak';
}
/**
* @return string
*/
public function getLoginURL(): string
{
return $this->getRealmBaseURL() . '/protocol/openid-connect/auth?' . \http_build_query([
'client_id' => $this->appID,
'redirect_uri' => $this->callback,
'state' => \json_encode($this->state),
'scope' => \implode(' ', $this->getScopes()),
'response_type' => 'code'
]);
}
/**
* @param string $code
*
* @return array
*/
protected function getTokens(string $code): array
{
if (empty($this->tokens)) {
$headers = ['Content-Type: application/x-www-form-urlencoded'];
$this->tokens = \json_decode($this->request(
'POST',
$this->getRealmBaseURL() . '/protocol/openid-connect/token',
$headers,
\http_build_query([
'code' => $code,
'client_id' => $this->appID,
'client_secret' => $this->getClientSecret(),
'redirect_uri' => $this->callback,
'scope' => \implode(' ', $this->getScopes()),
'grant_type' => 'authorization_code'
])
), true);
}
return $this->tokens;
}
/**
* @param string $refreshToken
*
* @return array
*/
public function refreshTokens(string $refreshToken): array
{
$headers = ['Content-Type: application/x-www-form-urlencoded'];
$this->tokens = \json_decode($this->request(
'POST',
$this->getRealmBaseURL() . '/protocol/openid-connect/token',
$headers,
\http_build_query([
'refresh_token' => $refreshToken,
'client_id' => $this->appID,
'client_secret' => $this->getClientSecret(),
'grant_type' => 'refresh_token'
])
), true);
if (empty($this->tokens['refresh_token'])) {
$this->tokens['refresh_token'] = $refreshToken;
}
return $this->tokens;
}
/**
* @param string $accessToken
*
* @return string
*/
public function getUserID(string $accessToken): string
{
$user = $this->getUser($accessToken);
if (isset($user['sub'])) {
return $user['sub'];
}
return '';
}
/**
* @param string $accessToken
*
* @return string
*/
public function getUserEmail(string $accessToken): string
{
$user = $this->getUser($accessToken);
if (isset($user['email'])) {
return $user['email'];
}
return '';
}
/**
* Check if the User email is verified
*
* @param string $accessToken
*
* @return bool
*/
public function isEmailVerified(string $accessToken): bool
{
$user = $this->getUser($accessToken);
if ($user['email_verified'] ?? false) {
return true;
}
return false;
}
/**
* @param string $accessToken
*
* @return string
*/
public function getUserName(string $accessToken): string
{
$user = $this->getUser($accessToken);
if (isset($user['name'])) {
return $user['name'];
}
return '';
}
/**
* @param string $accessToken
*
* @return array
*/
protected function getUser(string $accessToken): array
{
if (empty($this->user)) {
$headers = ['Authorization: Bearer ' . \urlencode($accessToken)];
$user = $this->request('GET', $this->getRealmBaseURL() . '/protocol/openid-connect/userinfo', $headers);
$this->user = \json_decode($user, true);
}
return $this->user;
}
/**
* Extracts the Client Secret from the JSON stored in appSecret
*
* @return string
*/
protected function getClientSecret(): string
{
$secret = $this->getAppSecret();
return $secret['clientSecret'] ?? '';
}
/**
* Extracts the Keycloak Domain from the JSON stored in appSecret
*
* @return string
*/
protected function getKeycloakDomain(): string
{
$secret = $this->getAppSecret();
return $secret['keycloakDomain'] ?? '';
}
/**
* Extracts the Keycloak Realm from the JSON stored in appSecret
*
* @return string
*/
protected function getKeycloakRealm(): string
{
$secret = $this->getAppSecret();
return $secret['keycloakRealm'] ?? '';
}
/**
* Build the realm-scoped base URL: `https://{domain}/realms/{realm}`.
* Keycloak realm names allow spaces and other characters that must be
* percent-encoded in URLs (e.g. `my realm` → `my%20realm`).
*
* @return string
*/
protected function getRealmBaseURL(): string
{
return 'https://' . $this->getKeycloakDomain() . '/realms/' . \rawurlencode($this->getKeycloakRealm());
}
/**
* Decode the JSON stored in appSecret
*
* @return array
*/
protected function getAppSecret(): array
{
try {
$secret = \json_decode($this->appSecret, true, 512, JSON_THROW_ON_ERROR);
} catch (\Throwable $th) {
throw new \Exception('Invalid secret');
}
return $secret;
}
}
+230
View File
@@ -0,0 +1,230 @@
<?php
namespace Appwrite\Auth\OAuth2;
use Appwrite\Auth\OAuth2;
// Reference Material
// https://docs.kick.com/getting-started/generating-tokens-oauth2-flow
// https://docs.kick.com/getting-started/scopes
class Kick extends OAuth2
{
private const PKCE_STATE_KEY = '_pkce';
/**
* @var array
*/
protected array $user = [];
/**
* @var array
*/
protected array $tokens = [];
/**
* @var array
*/
protected array $scopes = [
'user:read',
];
/**
* @var string
*/
private string $pkceVerifier = '';
/**
* @return string
*/
public function getName(): string
{
return 'kick';
}
/**
* @return string
*/
public function getLoginURL(): string
{
$state = $this->state;
$state[self::PKCE_STATE_KEY] = $this->getPKCEVerifier();
return 'https://id.kick.com/oauth/authorize?' . \http_build_query([
'response_type' => 'code',
'client_id' => $this->appID,
'redirect_uri' => $this->callback,
'scope' => \implode(' ', $this->getScopes()),
'state' => \json_encode($state),
'code_challenge' => $this->getPKCEChallenge(),
'code_challenge_method' => 'S256',
]);
}
/**
* @param string $code
*
* @return array
*/
protected function getTokens(string $code): array
{
if (empty($this->tokens)) {
$headers = ['Content-Type: application/x-www-form-urlencoded'];
$this->tokens = \json_decode($this->request(
'POST',
'https://id.kick.com/oauth/token',
$headers,
\http_build_query([
'grant_type' => 'authorization_code',
'client_id' => $this->appID,
'client_secret' => $this->appSecret,
'redirect_uri' => $this->callback,
'code_verifier' => $this->getPKCEVerifier(),
'code' => $code,
])
), true);
}
return $this->tokens;
}
/**
* @param string $refreshToken
*
* @return array
*/
public function refreshTokens(string $refreshToken): array
{
$headers = ['Content-Type: application/x-www-form-urlencoded'];
$this->tokens = \json_decode($this->request(
'POST',
'https://id.kick.com/oauth/token',
$headers,
\http_build_query([
'grant_type' => 'refresh_token',
'client_id' => $this->appID,
'client_secret' => $this->appSecret,
'refresh_token' => $refreshToken,
])
), true);
if (empty($this->tokens['refresh_token'])) {
$this->tokens['refresh_token'] = $refreshToken;
}
return $this->tokens;
}
/**
* @param string $accessToken
*
* @return string
*/
public function getUserID(string $accessToken): string
{
$user = $this->getUser($accessToken);
return isset($user['user_id']) ? (string)$user['user_id'] : '';
}
/**
* @param string $accessToken
*
* @return string
*/
public function getUserEmail(string $accessToken): string
{
$user = $this->getUser($accessToken);
return $user['email'] ?? '';
}
/**
* Check if the OAuth email is verified.
*
* Kick only returns an email when the user has granted the `user:read`
* scope and the account email is verified, so a non-empty email is
* treated as verified.
*
* @param string $accessToken
*
* @return bool
*/
public function isEmailVerified(string $accessToken): bool
{
return !empty($this->getUserEmail($accessToken));
}
/**
* @param string $accessToken
*
* @return string
*/
public function getUserName(string $accessToken): string
{
$user = $this->getUser($accessToken);
return $user['name'] ?? '';
}
/**
* @param string $accessToken
*
* @return array
*/
protected function getUser(string $accessToken): array
{
if (empty($this->user)) {
$headers = ['Authorization: Bearer ' . $accessToken];
$response = \json_decode($this->request(
'GET',
'https://api.kick.com/public/v1/users',
$headers
), true);
$this->user = $response['data'][0] ?? [];
}
return $this->user;
}
/**
* Extract the PKCE verifier from the state on the callback so the same
* value generated in getLoginURL() can be sent to the token endpoint.
*
* @param string $state
*
* @return array<string, mixed>|null
*/
public function parseState(string $state): ?array
{
$parsed = \json_decode($state, true);
if (!\is_array($parsed)) {
return null;
}
$verifier = $parsed[self::PKCE_STATE_KEY] ?? null;
if (\is_string($verifier)) {
$this->pkceVerifier = $verifier;
}
unset($parsed[self::PKCE_STATE_KEY]);
return $parsed;
}
private function getPKCEVerifier(): string
{
if ($this->pkceVerifier === '') {
$this->pkceVerifier = \rtrim(\strtr(\base64_encode(\random_bytes(64)), '+/', '-_'), '=');
}
return $this->pkceVerifier;
}
private function getPKCEChallenge(): string
{
return \rtrim(\strtr(\base64_encode(\hash('sha256', $this->getPKCEVerifier(), true)), '+/', '-_'), '=');
}
}
@@ -0,0 +1,80 @@
<?php
namespace Appwrite\Platform\Modules\Console\Http\OAuth2Providers;
use Appwrite\Platform\Modules\Project\Http\Project\OAuth2\Base as OAuth2Base;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\ContentType;
use Appwrite\SDK\Method;
use Appwrite\SDK\Response as SDKResponse;
use Appwrite\Utopia\Response;
use Utopia\Config\Config;
use Utopia\Database\Document;
use Utopia\Platform\Action;
use Utopia\Platform\Scope\HTTP;
class XList extends Action
{
use HTTP;
public static function getName(): string
{
return 'listOAuth2Providers';
}
public function __construct()
{
$this
->setHttpMethod(Action::HTTP_REQUEST_METHOD_GET)
->setHttpPath('/v1/console/oauth2-providers')
->desc('List OAuth2 providers')
->groups(['api'])
->label('scope', 'public')
->label('sdk', new Method(
namespace: 'console',
group: 'console',
name: 'listOAuth2Providers',
description: '/docs/references/console/list-oauth2-providers.md',
auth: [AuthType::ADMIN],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
model: Response::MODEL_CONSOLE_OAUTH2_PROVIDER_LIST,
)
],
contentType: ContentType::JSON
))
->inject('response')
->callback($this->action(...));
}
public function action(Response $response): void
{
$providersConfig = Config::getParam('oAuthProviders', []);
$actions = OAuth2Base::getProviderActions();
$providers = [];
foreach ($actions as $providerId => $updateClass) {
$config = $providersConfig[$providerId] ?? null;
if ($config === null) {
continue;
}
if (!($config['enabled'] ?? false)) {
continue;
}
if ($config['mock'] ?? false) {
continue;
}
$providers[] = new Document([
'$id' => $providerId,
'parameters' => $updateClass::getParameters(),
]);
}
$response->dynamic(new Document([
'total' => \count($providers),
'oAuth2Providers' => $providers,
]), Response::MODEL_CONSOLE_OAUTH2_PROVIDER_LIST);
}
}
@@ -5,6 +5,7 @@ namespace Appwrite\Platform\Modules\Console\Services;
use Appwrite\Platform\Modules\Console\Http\Assistant\Create as CreateAssistantQuery;
use Appwrite\Platform\Modules\Console\Http\Init\API;
use Appwrite\Platform\Modules\Console\Http\Init\Web;
use Appwrite\Platform\Modules\Console\Http\OAuth2Providers\XList as ListOAuth2Providers;
use Appwrite\Platform\Modules\Console\Http\Redirects\Auth\Get as RedirectAuth;
use Appwrite\Platform\Modules\Console\Http\Redirects\Card\Get as RedirectCard;
use Appwrite\Platform\Modules\Console\Http\Redirects\Invite\Get as RedirectInvite;
@@ -28,6 +29,7 @@ class Http extends Service
$this->addAction(Web::getName(), new Web());
$this->addAction(GetVariables::getName(), new GetVariables());
$this->addAction(ListOAuth2Providers::getName(), new ListOAuth2Providers());
$this->addAction(CreateAssistantQuery::getName(), new CreateAssistantQuery());
$this->addAction(GetResourceAvailability::getName(), new GetResourceAvailability());
@@ -0,0 +1,55 @@
<?php
namespace Appwrite\Platform\Modules\Project\Http\Project\OAuth2\Amazon;
use Appwrite\Auth\OAuth2\Amazon;
use Appwrite\Platform\Modules\Project\Http\Project\OAuth2\Base;
use Appwrite\Utopia\Response;
class Update extends Base
{
public static function getProviderId(): string
{
return 'amazon';
}
public static function getProviderClass(): string
{
return Amazon::class;
}
public static function getProviderLabel(): string
{
return 'Amazon';
}
public static function getProviderSDKMethod(): string
{
return 'updateOAuth2Amazon';
}
public static function getResponseModel(): string
{
return Response::MODEL_OAUTH2_AMAZON;
}
public static function getClientIdName(): string
{
return 'Client ID';
}
public static function getClientIdExample(): string
{
return 'amzn1.application-oa2-client.87400c00000000000000000000063d5b2';
}
public static function getClientSecretName(): string
{
return 'Client Secret';
}
public static function getClientSecretExample(): string
{
return '79ffe4000000000000000000000000000000000000000000000000000002de55';
}
}
@@ -0,0 +1,206 @@
<?php
namespace Appwrite\Platform\Modules\Project\Http\Project\OAuth2\Apple;
use Appwrite\Auth\OAuth2\Apple;
use Appwrite\Event\Event as QueueEvent;
use Appwrite\Platform\Action;
use Appwrite\Platform\Modules\Project\Http\Project\OAuth2\Base;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\Method;
use Appwrite\SDK\Response as SDKResponse;
use Appwrite\Utopia\Response;
use Utopia\Database\Database;
use Utopia\Database\Document;
use Utopia\Database\Validator\Authorization;
use Utopia\Validator\Boolean;
use Utopia\Validator\Nullable;
use Utopia\Validator\Text;
class Update extends Base
{
public static function getProviderId(): string
{
return 'apple';
}
public static function getProviderClass(): string
{
return Apple::class;
}
public static function getProviderLabel(): string
{
return 'Apple';
}
public static function getProviderSDKMethod(): string
{
return 'updateOAuth2Apple';
}
public static function getResponseModel(): string
{
return Response::MODEL_OAUTH2_APPLE;
}
public static function getClientIdParamName(): string
{
return 'serviceId';
}
public static function getClientIdName(): string
{
return 'Service ID';
}
public static function getClientIdExample(): string
{
return 'ip.appwrite.app.web';
}
public static function getClientSecretName(): string
{
// Apple does not use a single clientSecret param. Returning an empty
// string causes the default getParameters() to skip it; the override
// below adds the three real fields (keyId, teamId, p8File).
return '';
}
public static function getClientSecretExample(): string
{
return '';
}
public static function getParameters(): array
{
return [
[
'$id' => static::getClientIdParamName(),
'name' => static::getClientIdName(),
'example' => static::getClientIdExample(),
'hint' => '',
],
[
'$id' => 'keyId',
'name' => 'Key ID',
'example' => 'P4000000N8',
'hint' => '',
],
[
'$id' => 'teamId',
'name' => 'Team ID',
'example' => 'D4000000R6',
'hint' => '',
],
[
'$id' => 'p8File',
'name' => 'P8 File',
'example' => '-----BEGIN PRIVATE KEY-----MIGTAg...jy2Xbna-----END PRIVATE KEY-----',
'hint' => '',
],
];
}
public function __construct()
{
$providerId = static::getProviderId();
$providerLabel = static::getProviderLabel();
$this
->setHttpMethod(Action::HTTP_REQUEST_METHOD_PATCH)
->setHttpPath('/v1/project/oauth2/' . $providerId)
->desc('Update project OAuth2 ' . $providerLabel)
->groups(['api', 'project'])
->label('scope', 'oauth2.write')
->label('event', 'oauth2.[providerId].update')
->label('audits.event', 'project.oauth2.[providerId].update')
->label('audits.resource', 'project.oauth2/{response.$id}')
->label('sdk', new Method(
namespace: 'project',
group: 'oauth2',
name: static::getProviderSDKMethod(),
description: 'Update the project OAuth2 ' . $providerLabel . ' configuration.',
auth: [AuthType::ADMIN, AuthType::KEY],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
model: static::getResponseModel(),
)
],
))
->param(static::getClientIdParamName(), null, new Nullable(new Text(256, 0)), static::getClientIdDescription(), optional: true)
->param('keyId', null, new Nullable(new Text(256, 0)), '\'Key ID\' of Apple OAuth2 app. For example: P4000000N8', optional: true)
->param('teamId', null, new Nullable(new Text(256, 0)), '\'Team ID\' of Apple OAuth2 app. For example: D4000000R6', optional: true)
->param('p8File', null, new Nullable(new Text(4096, 0)), 'Contents of the Apple OAuth2 app .p8 private key file. The secret key wrapped by the PEM markers is 200 characters long. For example: -----BEGIN PRIVATE KEY-----MIGTAg...jy2Xbna-----END PRIVATE KEY-----', optional: true)
->param('enabled', null, new Nullable(new Boolean()), 'OAuth2 sign-in method status. Set to true to enable new session creation. Setting to true will trigger end-to-end credentials validation, and will throw if the credentials are invalid.', true)
->inject('response')
->inject('dbForPlatform')
->inject('project')
->inject('authorization')
->inject('queueForEvents')
->callback($this->handle(...));
}
public function buildReadResponse(Document $project): Document
{
$providerId = static::getProviderId();
$oAuthProviders = $project->getAttribute('oAuthProviders', []);
return new Document([
'$id' => $providerId,
'enabled' => $oAuthProviders[$providerId . 'Enabled'] ?? false,
static::getClientIdParamName() => $oAuthProviders[$providerId . 'Appid'] ?? '',
'keyId' => '',
'teamId' => '',
'p8File' => '',
]);
}
/**
* Custom callback used instead of the parent's `action()` because Apple's
* client secret is composed of three fields (.p8 file contents, Key ID and
* Team ID) that must be JSON-encoded to match the shape Apple's OAuth2
* adapter expects in getAppSecret(). The method is named differently to
* avoid an LSP-incompatible override of Base::action().
*/
public function handle(
?string $serviceId,
?string $keyId,
?string $teamId,
?string $p8File,
?bool $enabled,
Response $response,
Database $dbForPlatform,
Document $project,
Authorization $authorization,
QueueEvent $queueForEvents
): void {
$providerId = static::getProviderId();
$queueForEvents->setParam('providerId', $providerId);
// The secret is stored as JSON `{"p8": "...", "keyID": "...", "teamID": "..."}`
// to match the shape Apple's OAuth2 adapter expects in getAppSecret().
// Merge new values with what's already stored so that submitting only
// some of the fields leaves the rest untouched.
$encodedSecret = null;
if (!\is_null($keyId) || !\is_null($teamId) || !\is_null($p8File)) {
$storedRaw = $project->getAttribute('oAuthProviders', [])[$providerId . 'Secret'] ?? '';
$existing = [];
if (!empty($storedRaw)) {
$existing = \json_decode($storedRaw, true) ?: [];
}
$encodedSecret = \json_encode([
'p8' => $p8File ?? ($existing['p8'] ?? ''),
'keyID' => $keyId ?? ($existing['keyID'] ?? ''),
'teamID' => $teamId ?? ($existing['teamID'] ?? ''),
]);
}
$project = $this->persistCredentials($project, $dbForPlatform, $authorization, $serviceId, $encodedSecret, $enabled);
// Reuse buildReadResponse to keep PATCH/GET shapes identical and
// guarantee keyId/teamId/p8File are write-only on every response path.
$response->dynamic($this->buildReadResponse($project), static::getResponseModel());
}
}
@@ -0,0 +1,175 @@
<?php
namespace Appwrite\Platform\Modules\Project\Http\Project\OAuth2\Auth0;
use Appwrite\Auth\OAuth2\Auth0;
use Appwrite\Event\Event as QueueEvent;
use Appwrite\Platform\Action;
use Appwrite\Platform\Modules\Project\Http\Project\OAuth2\Base;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\Method;
use Appwrite\SDK\Response as SDKResponse;
use Appwrite\Utopia\Response;
use Utopia\Database\Database;
use Utopia\Database\Document;
use Utopia\Database\Validator\Authorization;
use Utopia\Validator\Boolean;
use Utopia\Validator\Nullable;
use Utopia\Validator\Text;
class Update extends Base
{
public static function getProviderId(): string
{
return 'auth0';
}
public static function getProviderClass(): string
{
return Auth0::class;
}
public static function getProviderLabel(): string
{
return 'Auth0';
}
public static function getProviderSDKMethod(): string
{
return 'updateOAuth2Auth0';
}
public static function getResponseModel(): string
{
return Response::MODEL_OAUTH2_AUTH0;
}
public static function getClientIdName(): string
{
return 'Client ID';
}
public static function getClientIdExample(): string
{
return 'OaOkIA000000000000000000005KLSYq';
}
public static function getClientSecretName(): string
{
return 'Client Secret';
}
public static function getClientSecretExample(): string
{
return 'zXz0000-00000000000000000000000000000-00000000000000000000PJafnF';
}
public static function getParameters(): array
{
return \array_merge(parent::getParameters(), [
[
'$id' => 'endpoint',
'name' => 'Domain',
'example' => 'example.us.auth0.com',
'hint' => '',
],
]);
}
public function __construct()
{
$providerId = static::getProviderId();
$providerLabel = static::getProviderLabel();
$this
->setHttpMethod(Action::HTTP_REQUEST_METHOD_PATCH)
->setHttpPath('/v1/project/oauth2/' . $providerId)
->desc('Update project OAuth2 ' . $providerLabel)
->groups(['api', 'project'])
->label('scope', 'oauth2.write')
->label('event', 'oauth2.[providerId].update')
->label('audits.event', 'project.oauth2.[providerId].update')
->label('audits.resource', 'project.oauth2/{response.$id}')
->label('sdk', new Method(
namespace: 'project',
group: 'oauth2',
name: static::getProviderSDKMethod(),
description: 'Update the project OAuth2 ' . $providerLabel . ' configuration.',
auth: [AuthType::ADMIN, AuthType::KEY],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
model: static::getResponseModel(),
)
],
))
->param(static::getClientIdParamName(), null, new Nullable(new Text(256, 0)), static::getClientIdDescription(), optional: true)
->param(static::getClientSecretParamName(), null, new Nullable(new Text(512, 0)), static::getClientSecretDescription(), optional: true)
->param('endpoint', null, new Nullable(new Text(256, 0)), 'Domain of Auth0 instance. For example: example.us.auth0.com', optional: true)
->param('enabled', null, new Nullable(new Boolean()), 'OAuth2 sign-in method status. Set to true to enable new session creation. Setting to true will trigger end-to-end credentials validation, and will throw if the credentials are invalid.', true)
->inject('response')
->inject('dbForPlatform')
->inject('project')
->inject('authorization')
->inject('queueForEvents')
->callback($this->handle(...));
}
public function buildReadResponse(Document $project): Document
{
$providerId = static::getProviderId();
$oAuthProviders = $project->getAttribute('oAuthProviders', []);
$decoded = $this->decodeStoredSecret($project);
return new Document([
'$id' => $providerId,
'enabled' => $oAuthProviders[$providerId . 'Enabled'] ?? false,
static::getClientIdParamName() => $oAuthProviders[$providerId . 'Appid'] ?? '',
static::getClientSecretParamName() => '',
'endpoint' => $decoded['auth0Domain'] ?? '',
]);
}
/**
* Custom callback used instead of the parent's `action()` because Auth0
* takes an additional optional `endpoint` parameter. The method is named
* differently to avoid an LSP-incompatible override of Base::action().
*/
public function handle(
?string $clientId,
?string $clientSecret,
?string $endpoint,
?bool $enabled,
Response $response,
Database $dbForPlatform,
Document $project,
Authorization $authorization,
QueueEvent $queueForEvents
): void {
$providerId = static::getProviderId();
$queueForEvents->setParam('providerId', $providerId);
// The secret is stored as JSON `{"clientSecret": "...", "auth0Domain": "..."}`
// to match the shape Auth0's OAuth2 adapter expects (getAuth0Domain()).
// Merge new values with existing storage so that submitting only one of
// `clientSecret`/`endpoint` leaves the other untouched.
$encodedSecret = null;
if (!\is_null($clientSecret) || !\is_null($endpoint)) {
$storedRaw = $project->getAttribute('oAuthProviders', [])[$providerId . 'Secret'] ?? '';
$existing = [];
if (!empty($storedRaw)) {
$existing = \json_decode($storedRaw, true) ?: [];
}
$encodedSecret = \json_encode([
'clientSecret' => $clientSecret ?? ($existing['clientSecret'] ?? ''),
'auth0Domain' => $endpoint ?? ($existing['auth0Domain'] ?? ''),
]);
}
$project = $this->persistCredentials($project, $dbForPlatform, $authorization, $clientId, $encodedSecret, $enabled);
// Reuse buildReadResponse to keep PATCH/GET shapes identical and
// guarantee the clientSecret is write-only on every response path.
$response->dynamic($this->buildReadResponse($project), static::getResponseModel());
}
}
@@ -0,0 +1,172 @@
<?php
namespace Appwrite\Platform\Modules\Project\Http\Project\OAuth2\Authentik;
use Appwrite\Auth\OAuth2\Authentik;
use Appwrite\Event\Event as QueueEvent;
use Appwrite\Platform\Action;
use Appwrite\Platform\Modules\Project\Http\Project\OAuth2\Base;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\Method;
use Appwrite\SDK\Response as SDKResponse;
use Appwrite\Utopia\Response;
use Utopia\Database\Database;
use Utopia\Database\Document;
use Utopia\Database\Validator\Authorization;
use Utopia\Validator\Boolean;
use Utopia\Validator\Nullable;
use Utopia\Validator\Text;
class Update extends Base
{
public static function getProviderId(): string
{
return 'authentik';
}
public static function getProviderClass(): string
{
return Authentik::class;
}
public static function getProviderLabel(): string
{
return 'Authentik';
}
public static function getProviderSDKMethod(): string
{
return 'updateOAuth2Authentik';
}
public static function getResponseModel(): string
{
return Response::MODEL_OAUTH2_AUTHENTIK;
}
public static function getClientIdName(): string
{
return 'Client ID';
}
public static function getClientIdExample(): string
{
return 'dTKOPa0000000000000000000000000000e7G8hv';
}
public static function getClientSecretName(): string
{
return 'Client Secret';
}
public static function getClientSecretExample(): string
{
return 'ntQadq000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000Hp5WK';
}
public static function getParameters(): array
{
return \array_merge(parent::getParameters(), [
[
'$id' => 'endpoint',
'name' => 'Domain',
'example' => 'example.authentik.com',
'hint' => '',
],
]);
}
public function __construct()
{
$providerId = static::getProviderId();
$providerLabel = static::getProviderLabel();
$this
->setHttpMethod(Action::HTTP_REQUEST_METHOD_PATCH)
->setHttpPath('/v1/project/oauth2/' . $providerId)
->desc('Update project OAuth2 ' . $providerLabel)
->groups(['api', 'project'])
->label('scope', 'oauth2.write')
->label('event', 'oauth2.[providerId].update')
->label('audits.event', 'project.oauth2.[providerId].update')
->label('audits.resource', 'project.oauth2/{response.$id}')
->label('sdk', new Method(
namespace: 'project',
group: 'oauth2',
name: static::getProviderSDKMethod(),
description: 'Update the project OAuth2 ' . $providerLabel . ' configuration.',
auth: [AuthType::ADMIN, AuthType::KEY],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
model: static::getResponseModel(),
)
],
))
->param(static::getClientIdParamName(), null, new Nullable(new Text(256, 0)), static::getClientIdDescription(), optional: true)
->param(static::getClientSecretParamName(), null, new Nullable(new Text(512, 0)), static::getClientSecretDescription(), optional: true)
->param('endpoint', '', new Text(256, 1), 'Domain of Authentik instance. For example: example.authentik.com', optional: false)
->param('enabled', null, new Nullable(new Boolean()), 'OAuth2 sign-in method status. Set to true to enable new session creation. Setting to true will trigger end-to-end credentials validation, and will throw if the credentials are invalid.', true)
->inject('response')
->inject('dbForPlatform')
->inject('project')
->inject('authorization')
->inject('queueForEvents')
->callback($this->handle(...));
}
public function buildReadResponse(Document $project): Document
{
$providerId = static::getProviderId();
$oAuthProviders = $project->getAttribute('oAuthProviders', []);
$decoded = $this->decodeStoredSecret($project);
return new Document([
'$id' => $providerId,
'enabled' => $oAuthProviders[$providerId . 'Enabled'] ?? false,
static::getClientIdParamName() => $oAuthProviders[$providerId . 'Appid'] ?? '',
static::getClientSecretParamName() => '',
'endpoint' => $decoded['authentikDomain'] ?? '',
]);
}
/**
* Custom callback used instead of the parent's `action()` because Authentik
* takes an additional required `endpoint` parameter. The method is named
* differently to avoid an LSP-incompatible override of Base::action().
*/
public function handle(
?string $clientId,
?string $clientSecret,
string $endpoint,
?bool $enabled,
Response $response,
Database $dbForPlatform,
Document $project,
Authorization $authorization,
QueueEvent $queueForEvents
): void {
$providerId = static::getProviderId();
$queueForEvents->setParam('providerId', $providerId);
// The secret is stored as JSON `{"clientSecret": "...", "authentikDomain": "..."}`
// to match the shape Authentik's OAuth2 adapter expects (getAuthentikDomain()).
// The `endpoint` param is required on every call, so it's always written.
// `clientSecret` is optional; if omitted, the existing stored secret is preserved.
$storedRaw = $project->getAttribute('oAuthProviders', [])[$providerId . 'Secret'] ?? '';
$existing = [];
if (!empty($storedRaw)) {
$existing = \json_decode($storedRaw, true) ?: [];
}
$encodedSecret = \json_encode([
'clientSecret' => $clientSecret ?? ($existing['clientSecret'] ?? ''),
'authentikDomain' => $endpoint,
]);
$project = $this->persistCredentials($project, $dbForPlatform, $authorization, $clientId, $encodedSecret, $enabled);
// Reuse buildReadResponse to keep PATCH/GET shapes identical and
// guarantee the clientSecret is write-only on every response path.
$response->dynamic($this->buildReadResponse($project), static::getResponseModel());
}
}
@@ -0,0 +1,55 @@
<?php
namespace Appwrite\Platform\Modules\Project\Http\Project\OAuth2\Autodesk;
use Appwrite\Auth\OAuth2\Autodesk;
use Appwrite\Platform\Modules\Project\Http\Project\OAuth2\Base;
use Appwrite\Utopia\Response;
class Update extends Base
{
public static function getProviderId(): string
{
return 'autodesk';
}
public static function getProviderClass(): string
{
return Autodesk::class;
}
public static function getProviderLabel(): string
{
return 'Autodesk';
}
public static function getProviderSDKMethod(): string
{
return 'updateOAuth2Autodesk';
}
public static function getResponseModel(): string
{
return Response::MODEL_OAUTH2_AUTODESK;
}
public static function getClientIdName(): string
{
return 'Client ID';
}
public static function getClientIdExample(): string
{
return '5zw90v00000000000000000000kVYXN7';
}
public static function getClientSecretName(): string
{
return 'Client Secret';
}
public static function getClientSecretExample(): string
{
return '7I000000000000MW';
}
}
@@ -0,0 +1,449 @@
<?php
namespace Appwrite\Platform\Modules\Project\Http\Project\OAuth2;
use Appwrite\Event\Event as QueueEvent;
use Appwrite\Extend\Exception;
use Appwrite\Platform\Action;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\Method;
use Appwrite\SDK\Response as SDKResponse;
use Appwrite\Utopia\Response;
use Utopia\Config\Config;
use Utopia\Database\Database;
use Utopia\Database\Document;
use Utopia\Database\Validator\Authorization;
use Utopia\Platform\Scope\HTTP;
use Utopia\Validator\Boolean;
use Utopia\Validator\Nullable;
use Utopia\Validator\Text;
abstract class Base extends Action
{
use HTTP;
/**
* Provider ID used in paths, database keys and event labels.
*
* @return string e.g. 'github', 'discord', 'figma'
*/
abstract public static function getProviderId(): string;
/**
* Provider OAuth2 implementation class. Must implement verifyCredentials().
*
* @return class-string e.g. Github::class
*/
abstract public static function getProviderClass(): string;
/**
* Provider display label used in descriptions, SDK method name and action name.
*
* @return string e.g. 'GitHub', 'Discord', 'Figma'
*/
abstract public static function getProviderLabel(): string;
/**
* Response model constant for this provider.
*
* @return string e.g. Response::MODEL_OAUTH2_GITHUB
*/
abstract public static function getResponseModel(): string;
/**
* Description of the clientId param, auto-built from
* {@see getClientIdName()}, {@see getClientIdExample()} and
* {@see getClientIdHint()}. Returns an empty string when the name is
* empty (e.g. providers like Apple that don't expose a single clientId
* description but still need to bypass this default).
*/
public static function getClientIdDescription(): string
{
return self::buildParamDescription(
static::getClientIdName(),
static::getClientIdExample(),
static::getClientIdHint()
);
}
/**
* Description of the clientSecret param, auto-built from
* {@see getClientSecretName()}, {@see getClientSecretExample()} and
* {@see getClientSecretHint()}. Returns an empty string when the name
* is empty (e.g. Apple, which uses keyId/teamId/p8File instead).
*/
public static function getClientSecretDescription(): string
{
return self::buildParamDescription(
static::getClientSecretName(),
static::getClientSecretExample(),
static::getClientSecretHint()
);
}
/**
* Format a parameter description as
* "'<name>' of <providerLabel> OAuth2 app. For example: <example>[. <hint>]".
* Returns an empty string when the name is empty.
*/
private static function buildParamDescription(string $name, string $example, string $hint): string
{
if ($name === '') {
return '';
}
$description = '\'' . $name . '\' of ' . static::getProviderLabel() . ' OAuth2 app. For example: ' . $example;
if ($hint !== '') {
$description .= '. ' . $hint;
}
return $description;
}
/**
* Verbose, user-facing name of the clientId param. Includes alternate
* names when the provider exposes more than one (e.g. "Client ID or App
* ID", "Application ID (also known as Client ID)").
*
* @return string
*/
abstract public static function getClientIdName(): string;
/**
* Example value of the clientId param. Used to build the public OAuth2
* providers metadata response.
*
* @return string
*/
abstract public static function getClientIdExample(): string;
/**
* Optional hint for the clientId param. Typically used to call out a
* common wrong value (e.g. "Example of wrong value: 370006"). Defaults
* to an empty string.
*/
public static function getClientIdHint(): string
{
return '';
}
/**
* Verbose, user-facing name of the clientSecret param. Returns an empty
* string for providers that don't have a single clientSecret param
* (e.g. Apple uses keyId/teamId/p8File instead).
*
* @return string
*/
abstract public static function getClientSecretName(): string;
/**
* Example value of the clientSecret param. Returns an empty string for
* providers without a clientSecret param.
*
* @return string
*/
abstract public static function getClientSecretExample(): string;
/**
* Optional hint for the clientSecret param. Defaults to an empty string.
*/
public static function getClientSecretHint(): string
{
return '';
}
/**
* Public-facing parameter metadata for this provider. Used by the public
* console OAuth2 providers endpoint to describe the form fields a project
* owner must fill in to configure the provider.
*
* Default shape: clientId + clientSecret. Providers that take additional
* fields (Apple, Auth0, Authentik, Gitlab, Microsoft, Oidc, Okta)
* override this method to add or replace entries. Each parameter is an
* associative array with keys `$id`, `name`, `example`, `hint`.
*
* @return array<int, array<string, string>>
*/
public static function getParameters(): array
{
$parameters = [];
$clientIdName = static::getClientIdName();
if ($clientIdName !== '') {
$parameters[] = [
'$id' => static::getClientIdParamName(),
'name' => $clientIdName,
'example' => static::getClientIdExample(),
'hint' => static::getClientIdHint(),
];
}
$clientSecretName = static::getClientSecretName();
if ($clientSecretName !== '') {
$parameters[] = [
'$id' => static::getClientSecretParamName(),
'name' => $clientSecretName,
'example' => static::getClientSecretExample(),
'hint' => static::getClientSecretHint(),
];
}
return $parameters;
}
/**
* Public-facing name of the clientId param. Some providers use a different
* terminology (e.g. Dropbox calls it "App key"), so the param name and the
* corresponding response field can be customized by overriding this method.
*
* @return string e.g. 'clientId' (default), 'appKey'
*/
public static function getClientIdParamName(): string
{
return 'clientId';
}
/**
* Public-facing name of the clientSecret param. Some providers use a
* different terminology (e.g. Dropbox calls it "App secret"), so the param
* name and the corresponding response field can be customized by
* overriding this method.
*
* @return string e.g. 'clientSecret' (default), 'appSecret'
*/
public static function getClientSecretParamName(): string
{
return 'clientSecret';
}
/**
* SDK method name exposed to clients.
*
* @return string e.g. 'updateOAuth2GitHub'
*/
abstract public static function getProviderSDKMethod(): string;
public static function getName()
{
return 'updateProjectOAuth2' . static::getProviderLabel();
}
public function __construct()
{
$providerId = static::getProviderId();
$providerLabel = static::getProviderLabel();
$this
->setHttpMethod(Action::HTTP_REQUEST_METHOD_PATCH)
->setHttpPath('/v1/project/oauth2/' . $providerId)
->desc('Update project OAuth2 ' . $providerLabel)
->groups(['api', 'project'])
->label('scope', 'oauth2.write')
->label('event', 'oauth2.[providerId].update')
->label('audits.event', 'project.oauth2.[providerId].update')
->label('audits.resource', 'project.oauth2/{response.$id}')
->label('sdk', new Method(
namespace: 'project',
group: 'oauth2',
name: static::getProviderSDKMethod(),
description: 'Update the project OAuth2 ' . $providerLabel . ' configuration.',
auth: [AuthType::ADMIN, AuthType::KEY],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
model: static::getResponseModel(),
)
],
))
->param(static::getClientIdParamName(), null, new Nullable(new Text(256, 0)), static::getClientIdDescription(), optional: true)
->param(static::getClientSecretParamName(), null, new Nullable(new Text(512, 0)), static::getClientSecretDescription(), optional: true)
->param('enabled', null, new Nullable(new Boolean()), 'OAuth2 sign-in method status. Set to true to enable new session creation. Setting to true will trigger end-to-end credentials validation, and will throw if the credentials are invalid.', true)
->inject('response')
->inject('dbForPlatform')
->inject('project')
->inject('authorization')
->inject('queueForEvents')
->callback($this->action(...));
}
/**
* Registry of provider ID -> Update action class. Mirrors the OAuth2
* actions registered in Project\Services\Http. Used by the Get and XList
* read endpoints to dispatch per-provider response shaping.
*
* @return array<string, class-string<Base>>
*/
public static function getProviderActions(): array
{
return [
'github' => GitHub\Update::class,
'discord' => Discord\Update::class,
'figma' => Figma\Update::class,
'dropbox' => Dropbox\Update::class,
'dailymotion' => Dailymotion\Update::class,
'bitbucket' => Bitbucket\Update::class,
'bitly' => Bitly\Update::class,
'box' => Box\Update::class,
'autodesk' => Autodesk\Update::class,
'google' => Google\Update::class,
'zoom' => Zoom\Update::class,
'zoho' => Zoho\Update::class,
'yandex' => Yandex\Update::class,
'x' => X\Update::class,
'wordpress' => WordPress\Update::class,
'twitch' => Twitch\Update::class,
'stripe' => Stripe\Update::class,
'spotify' => Spotify\Update::class,
'slack' => Slack\Update::class,
'podio' => Podio\Update::class,
'notion' => Notion\Update::class,
'salesforce' => Salesforce\Update::class,
'yahoo' => Yahoo\Update::class,
'linkedin' => Linkedin\Update::class,
'disqus' => Disqus\Update::class,
'amazon' => Amazon\Update::class,
'etsy' => Etsy\Update::class,
'facebook' => Facebook\Update::class,
'tradeshift' => Tradeshift\Update::class,
'tradeshiftBox' => TradeshiftSandbox\Update::class,
'paypal' => Paypal\Update::class,
'paypalSandbox' => PaypalSandbox\Update::class,
'gitlab' => Gitlab\Update::class,
'authentik' => Authentik\Update::class,
'auth0' => Auth0\Update::class,
'fusionauth' => FusionAuth\Update::class,
'keycloak' => Keycloak\Update::class,
'oidc' => Oidc\Update::class,
'okta' => Okta\Update::class,
'kick' => Kick\Update::class,
'apple' => Apple\Update::class,
'microsoft' => Microsoft\Update::class,
];
}
/**
* Build the read-only response document for this provider, with credential
* fields zeroed out (write-only). Default implementation handles providers
* that store a plain client ID + client secret. Special providers (Apple,
* Gitlab, Auth0, Authentik, Oidc, Okta) override to expose their
* non-secret extras (endpoint, domain, discovery URLs, ...) decoded from
* the JSON-encoded secret blob.
*/
public function buildReadResponse(Document $project): Document
{
$providerId = static::getProviderId();
$oAuthProviders = $project->getAttribute('oAuthProviders', []);
return new Document([
'$id' => $providerId,
'enabled' => $oAuthProviders[$providerId . 'Enabled'] ?? false,
static::getClientIdParamName() => $oAuthProviders[$providerId . 'Appid'] ?? '',
static::getClientSecretParamName() => '',
]);
}
/**
* Decode the JSON-encoded secret blob stored under `{providerId}Secret`.
* Returns an empty array when the value is empty or not valid JSON.
*/
protected function decodeStoredSecret(Document $project): array
{
$providerId = static::getProviderId();
$stored = $project->getAttribute('oAuthProviders', [])[$providerId . 'Secret'] ?? '';
if (empty($stored)) {
return [];
}
$decoded = \json_decode($stored, true);
return \is_array($decoded) ? $decoded : [];
}
/**
* Apply the provided credential changes to the project's oAuthProviders map,
* run the optional credential verification hook, persist the project, and
* return the updated project document.
*
* Providers that need to serialize multiple values into a single secret
* (e.g. GitLab, which stores `{clientSecret, endpoint}` as JSON) should
* encode those values into `$clientSecret` before calling this method.
*/
protected function persistCredentials(
Document $project,
Database $dbForPlatform,
Authorization $authorization,
?string $clientId,
?string $clientSecret,
?bool $enabled
): Document {
$providerId = static::getProviderId();
if (!(\in_array($providerId, \array_keys(Config::getParam('oAuthProviders'))))) {
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Provider ' . $providerId . ' is not supported by server configuration.');
}
$oAuthProviders = $project->getAttribute('oAuthProviders', []);
$appIdKey = $providerId . 'Appid';
$appSecretKey = $providerId . 'Secret';
$enabledKey = $providerId . 'Enabled';
if (!\is_null($clientId)) {
$oAuthProviders[$appIdKey] = $clientId;
}
if (!\is_null($clientSecret)) {
$oAuthProviders[$appSecretKey] = $clientSecret;
}
if (!\is_null($enabled)) {
$oAuthProviders[$enabledKey] = $enabled;
}
if ($enabled === true || \is_null($enabled)) {
try {
if (empty($oAuthProviders[$appIdKey]) || empty($oAuthProviders[$appSecretKey])) {
throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'Client ID and Client Secret are required when enabling OAuth2 provider.');
}
$providerClass = static::getProviderClass();
$providerInstance = new $providerClass(appId: $oAuthProviders[$appIdKey], appSecret: $oAuthProviders[$appSecretKey], callback: '', state: [], scopes: []);
// E2E integration check
if (\method_exists($providerInstance, 'verifyCredentials')) {
$providerInstance->verifyCredentials();
}
$oAuthProviders[$enabledKey] = true;
} catch (\Throwable $err) {
if ($enabled === true) {
throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'Could not enable OAuth2 provider: ' . $err->getMessage());
}
}
}
$updates = new Document([
'oAuthProviders' => $oAuthProviders
]);
return $authorization->skip(fn () => $dbForPlatform->updateDocument('projects', $project->getId(), $updates));
}
public function action(
?string $clientId,
?string $clientSecret,
?bool $enabled,
Response $response,
Database $dbForPlatform,
Document $project,
Authorization $authorization,
QueueEvent $queueForEvents
): void {
$project = $this->persistCredentials($project, $dbForPlatform, $authorization, $clientId, $clientSecret, $enabled);
$queueForEvents->setParam('providerId', static::getProviderId());
// Reuse buildReadResponse to keep PATCH/GET shapes identical and
// guarantee the clientSecret is write-only on every response path.
$response->dynamic($this->buildReadResponse($project), static::getResponseModel());
}
}
@@ -0,0 +1,65 @@
<?php
namespace Appwrite\Platform\Modules\Project\Http\Project\OAuth2\Bitbucket;
use Appwrite\Auth\OAuth2\Bitbucket;
use Appwrite\Platform\Modules\Project\Http\Project\OAuth2\Base;
use Appwrite\Utopia\Response;
class Update extends Base
{
public static function getProviderId(): string
{
return 'bitbucket';
}
public static function getProviderClass(): string
{
return Bitbucket::class;
}
public static function getProviderLabel(): string
{
return 'Bitbucket';
}
public static function getProviderSDKMethod(): string
{
return 'updateOAuth2Bitbucket';
}
public static function getResponseModel(): string
{
return Response::MODEL_OAUTH2_BITBUCKET;
}
public static function getClientIdParamName(): string
{
return 'key';
}
public static function getClientSecretParamName(): string
{
return 'secret';
}
public static function getClientIdName(): string
{
return 'Key';
}
public static function getClientIdExample(): string
{
return 'Knt70000000000ByRc';
}
public static function getClientSecretName(): string
{
return 'Secret';
}
public static function getClientSecretExample(): string
{
return 'NMfLZJ00000000000000000000TLQdDx';
}
}
@@ -0,0 +1,55 @@
<?php
namespace Appwrite\Platform\Modules\Project\Http\Project\OAuth2\Bitly;
use Appwrite\Auth\OAuth2\Bitly;
use Appwrite\Platform\Modules\Project\Http\Project\OAuth2\Base;
use Appwrite\Utopia\Response;
class Update extends Base
{
public static function getProviderId(): string
{
return 'bitly';
}
public static function getProviderClass(): string
{
return Bitly::class;
}
public static function getProviderLabel(): string
{
return 'Bitly';
}
public static function getProviderSDKMethod(): string
{
return 'updateOAuth2Bitly';
}
public static function getResponseModel(): string
{
return Response::MODEL_OAUTH2_BITLY;
}
public static function getClientIdName(): string
{
return 'Client ID';
}
public static function getClientIdExample(): string
{
return 'd95151000000000000000000000000000067af9b';
}
public static function getClientSecretName(): string
{
return 'Client Secret';
}
public static function getClientSecretExample(): string
{
return 'a13e250000000000000000000000000000d73095';
}
}
@@ -0,0 +1,55 @@
<?php
namespace Appwrite\Platform\Modules\Project\Http\Project\OAuth2\Box;
use Appwrite\Auth\OAuth2\Box;
use Appwrite\Platform\Modules\Project\Http\Project\OAuth2\Base;
use Appwrite\Utopia\Response;
class Update extends Base
{
public static function getProviderId(): string
{
return 'box';
}
public static function getProviderClass(): string
{
return Box::class;
}
public static function getProviderLabel(): string
{
return 'Box';
}
public static function getProviderSDKMethod(): string
{
return 'updateOAuth2Box';
}
public static function getResponseModel(): string
{
return Response::MODEL_OAUTH2_BOX;
}
public static function getClientIdName(): string
{
return 'Client ID';
}
public static function getClientIdExample(): string
{
return 'deglcs00000000000000000000x2og6y';
}
public static function getClientSecretName(): string
{
return 'Client Secret';
}
public static function getClientSecretExample(): string
{
return 'OKM1f100000000000000000000eshEif';
}
}
@@ -0,0 +1,65 @@
<?php
namespace Appwrite\Platform\Modules\Project\Http\Project\OAuth2\Dailymotion;
use Appwrite\Auth\OAuth2\Dailymotion;
use Appwrite\Platform\Modules\Project\Http\Project\OAuth2\Base;
use Appwrite\Utopia\Response;
class Update extends Base
{
public static function getProviderId(): string
{
return 'dailymotion';
}
public static function getProviderClass(): string
{
return Dailymotion::class;
}
public static function getProviderLabel(): string
{
return 'Dailymotion';
}
public static function getProviderSDKMethod(): string
{
return 'updateOAuth2Dailymotion';
}
public static function getResponseModel(): string
{
return Response::MODEL_OAUTH2_DAILYMOTION;
}
public static function getClientIdParamName(): string
{
return 'apiKey';
}
public static function getClientSecretParamName(): string
{
return 'apiSecret';
}
public static function getClientIdName(): string
{
return 'API Key';
}
public static function getClientIdExample(): string
{
return '07a9000000000000067f';
}
public static function getClientSecretName(): string
{
return 'API Secret';
}
public static function getClientSecretExample(): string
{
return 'a399a90000000000000000000000000000d90639';
}
}
@@ -0,0 +1,55 @@
<?php
namespace Appwrite\Platform\Modules\Project\Http\Project\OAuth2\Discord;
use Appwrite\Auth\OAuth2\Discord;
use Appwrite\Platform\Modules\Project\Http\Project\OAuth2\Base;
use Appwrite\Utopia\Response;
class Update extends Base
{
public static function getProviderId(): string
{
return 'discord';
}
public static function getProviderClass(): string
{
return Discord::class;
}
public static function getProviderLabel(): string
{
return 'Discord';
}
public static function getProviderSDKMethod(): string
{
return 'updateOAuth2Discord';
}
public static function getResponseModel(): string
{
return Response::MODEL_OAUTH2_DISCORD;
}
public static function getClientIdName(): string
{
return 'Client ID';
}
public static function getClientIdExample(): string
{
return '950722000000343754';
}
public static function getClientSecretName(): string
{
return 'Client Secret';
}
public static function getClientSecretExample(): string
{
return 'YmPXnM000000000000000000002zFg5D';
}
}
@@ -0,0 +1,65 @@
<?php
namespace Appwrite\Platform\Modules\Project\Http\Project\OAuth2\Disqus;
use Appwrite\Auth\OAuth2\Disqus;
use Appwrite\Platform\Modules\Project\Http\Project\OAuth2\Base;
use Appwrite\Utopia\Response;
class Update extends Base
{
public static function getProviderId(): string
{
return 'disqus';
}
public static function getProviderClass(): string
{
return Disqus::class;
}
public static function getProviderLabel(): string
{
return 'Disqus';
}
public static function getProviderSDKMethod(): string
{
return 'updateOAuth2Disqus';
}
public static function getResponseModel(): string
{
return Response::MODEL_OAUTH2_DISQUS;
}
public static function getClientIdParamName(): string
{
return 'publicKey';
}
public static function getClientSecretParamName(): string
{
return 'secretKey';
}
public static function getClientIdName(): string
{
return 'Public Key, also known as API Key';
}
public static function getClientIdExample(): string
{
return 'cgegH70000000000000000000000000000000000000000000000000000Hr1nYX';
}
public static function getClientSecretName(): string
{
return 'Secret Key, also known as API Secret';
}
public static function getClientSecretExample(): string
{
return 'W7Bykj00000000000000000000000000000000000000000000000000003o43w9';
}
}
@@ -0,0 +1,65 @@
<?php
namespace Appwrite\Platform\Modules\Project\Http\Project\OAuth2\Dropbox;
use Appwrite\Auth\OAuth2\Dropbox;
use Appwrite\Platform\Modules\Project\Http\Project\OAuth2\Base;
use Appwrite\Utopia\Response;
class Update extends Base
{
public static function getProviderId(): string
{
return 'dropbox';
}
public static function getProviderClass(): string
{
return Dropbox::class;
}
public static function getProviderLabel(): string
{
return 'Dropbox';
}
public static function getProviderSDKMethod(): string
{
return 'updateOAuth2Dropbox';
}
public static function getResponseModel(): string
{
return Response::MODEL_OAUTH2_DROPBOX;
}
public static function getClientIdParamName(): string
{
return 'appKey';
}
public static function getClientSecretParamName(): string
{
return 'appSecret';
}
public static function getClientIdName(): string
{
return 'App Key';
}
public static function getClientIdExample(): string
{
return 'jl000000000009t';
}
public static function getClientSecretName(): string
{
return 'App Secret';
}
public static function getClientSecretExample(): string
{
return 'g200000000000vw';
}
}
@@ -0,0 +1,65 @@
<?php
namespace Appwrite\Platform\Modules\Project\Http\Project\OAuth2\Etsy;
use Appwrite\Auth\OAuth2\Etsy;
use Appwrite\Platform\Modules\Project\Http\Project\OAuth2\Base;
use Appwrite\Utopia\Response;
class Update extends Base
{
public static function getProviderId(): string
{
return 'etsy';
}
public static function getProviderClass(): string
{
return Etsy::class;
}
public static function getProviderLabel(): string
{
return 'Etsy';
}
public static function getProviderSDKMethod(): string
{
return 'updateOAuth2Etsy';
}
public static function getResponseModel(): string
{
return Response::MODEL_OAUTH2_ETSY;
}
public static function getClientIdParamName(): string
{
return 'keyString';
}
public static function getClientSecretParamName(): string
{
return 'sharedSecret';
}
public static function getClientIdName(): string
{
return 'Keystring';
}
public static function getClientIdExample(): string
{
return 'nsgzxh0000000000008j85a2';
}
public static function getClientSecretName(): string
{
return 'Shared Secret';
}
public static function getClientSecretExample(): string
{
return 'tp000000ru';
}
}
@@ -0,0 +1,65 @@
<?php
namespace Appwrite\Platform\Modules\Project\Http\Project\OAuth2\Facebook;
use Appwrite\Auth\OAuth2\Facebook;
use Appwrite\Platform\Modules\Project\Http\Project\OAuth2\Base;
use Appwrite\Utopia\Response;
class Update extends Base
{
public static function getProviderId(): string
{
return 'facebook';
}
public static function getProviderClass(): string
{
return Facebook::class;
}
public static function getProviderLabel(): string
{
return 'Facebook';
}
public static function getProviderSDKMethod(): string
{
return 'updateOAuth2Facebook';
}
public static function getResponseModel(): string
{
return Response::MODEL_OAUTH2_FACEBOOK;
}
public static function getClientIdParamName(): string
{
return 'appId';
}
public static function getClientSecretParamName(): string
{
return 'appSecret';
}
public static function getClientIdName(): string
{
return 'App ID';
}
public static function getClientIdExample(): string
{
return '260600000007694';
}
public static function getClientSecretName(): string
{
return 'App Secret';
}
public static function getClientSecretExample(): string
{
return '2d0b2800000000000000000000d38af4';
}
}
@@ -0,0 +1,55 @@
<?php
namespace Appwrite\Platform\Modules\Project\Http\Project\OAuth2\Figma;
use Appwrite\Auth\OAuth2\Figma;
use Appwrite\Platform\Modules\Project\Http\Project\OAuth2\Base;
use Appwrite\Utopia\Response;
class Update extends Base
{
public static function getProviderId(): string
{
return 'figma';
}
public static function getProviderClass(): string
{
return Figma::class;
}
public static function getProviderLabel(): string
{
return 'Figma';
}
public static function getProviderSDKMethod(): string
{
return 'updateOAuth2Figma';
}
public static function getResponseModel(): string
{
return Response::MODEL_OAUTH2_FIGMA;
}
public static function getClientIdName(): string
{
return 'Client ID';
}
public static function getClientIdExample(): string
{
return 'byay5H0000000000VtiI40';
}
public static function getClientSecretName(): string
{
return 'Client Secret';
}
public static function getClientSecretExample(): string
{
return 'yEpOYn0000000000000000004iIsU5';
}
}
@@ -0,0 +1,172 @@
<?php
namespace Appwrite\Platform\Modules\Project\Http\Project\OAuth2\FusionAuth;
use Appwrite\Auth\OAuth2\FusionAuth;
use Appwrite\Event\Event as QueueEvent;
use Appwrite\Platform\Action;
use Appwrite\Platform\Modules\Project\Http\Project\OAuth2\Base;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\Method;
use Appwrite\SDK\Response as SDKResponse;
use Appwrite\Utopia\Response;
use Utopia\Database\Database;
use Utopia\Database\Document;
use Utopia\Database\Validator\Authorization;
use Utopia\Validator\Boolean;
use Utopia\Validator\Nullable;
use Utopia\Validator\Text;
class Update extends Base
{
public static function getProviderId(): string
{
return 'fusionauth';
}
public static function getProviderClass(): string
{
return FusionAuth::class;
}
public static function getProviderLabel(): string
{
return 'FusionAuth';
}
public static function getProviderSDKMethod(): string
{
return 'updateOAuth2FusionAuth';
}
public static function getResponseModel(): string
{
return Response::MODEL_OAUTH2_FUSIONAUTH;
}
public static function getClientIdName(): string
{
return 'Client ID';
}
public static function getClientIdExample(): string
{
return 'b2222c00-0000-0000-0000-000000862097';
}
public static function getClientSecretName(): string
{
return 'Client Secret';
}
public static function getClientSecretExample(): string
{
return 'Jx4s0C0000000000000000000000000000000wGqLsc';
}
public static function getParameters(): array
{
return \array_merge(parent::getParameters(), [
[
'$id' => 'endpoint',
'name' => 'Domain',
'example' => 'example.fusionauth.io',
'hint' => '',
],
]);
}
public function __construct()
{
$providerId = static::getProviderId();
$providerLabel = static::getProviderLabel();
$this
->setHttpMethod(Action::HTTP_REQUEST_METHOD_PATCH)
->setHttpPath('/v1/project/oauth2/' . $providerId)
->desc('Update project OAuth2 ' . $providerLabel)
->groups(['api', 'project'])
->label('scope', 'oauth2.write')
->label('event', 'oauth2.[providerId].update')
->label('audits.event', 'project.oauth2.[providerId].update')
->label('audits.resource', 'project.oauth2/{response.$id}')
->label('sdk', new Method(
namespace: 'project',
group: 'oauth2',
name: static::getProviderSDKMethod(),
description: 'Update the project OAuth2 ' . $providerLabel . ' configuration.',
auth: [AuthType::ADMIN, AuthType::KEY],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
model: static::getResponseModel(),
)
],
))
->param(static::getClientIdParamName(), null, new Nullable(new Text(256, 0)), static::getClientIdDescription(), optional: true)
->param(static::getClientSecretParamName(), null, new Nullable(new Text(512, 0)), static::getClientSecretDescription(), optional: true)
->param('endpoint', '', new Text(256, 1), 'Domain of FusionAuth instance. For example: example.fusionauth.io', optional: false)
->param('enabled', null, new Nullable(new Boolean()), 'OAuth2 sign-in method status. Set to true to enable new session creation. Setting to true will trigger end-to-end credentials validation, and will throw if the credentials are invalid.', true)
->inject('response')
->inject('dbForPlatform')
->inject('project')
->inject('authorization')
->inject('queueForEvents')
->callback($this->handle(...));
}
public function buildReadResponse(Document $project): Document
{
$providerId = static::getProviderId();
$oAuthProviders = $project->getAttribute('oAuthProviders', []);
$decoded = $this->decodeStoredSecret($project);
return new Document([
'$id' => $providerId,
'enabled' => $oAuthProviders[$providerId . 'Enabled'] ?? false,
static::getClientIdParamName() => $oAuthProviders[$providerId . 'Appid'] ?? '',
static::getClientSecretParamName() => '',
'endpoint' => $decoded['fusionAuthDomain'] ?? '',
]);
}
/**
* Custom callback used instead of the parent's `action()` because FusionAuth
* takes an additional required `endpoint` parameter. The method is named
* differently to avoid an LSP-incompatible override of Base::action().
*/
public function handle(
?string $clientId,
?string $clientSecret,
string $endpoint,
?bool $enabled,
Response $response,
Database $dbForPlatform,
Document $project,
Authorization $authorization,
QueueEvent $queueForEvents
): void {
$providerId = static::getProviderId();
$queueForEvents->setParam('providerId', $providerId);
// The secret is stored as JSON `{"clientSecret": "...", "fusionAuthDomain": "..."}`
// to match the shape FusionAuth's OAuth2 adapter expects (getFusionAuthDomain()).
// The `endpoint` param is required on every call, so it's always written.
// `clientSecret` is optional; if omitted, the existing stored secret is preserved.
$storedRaw = $project->getAttribute('oAuthProviders', [])[$providerId . 'Secret'] ?? '';
$existing = [];
if (!empty($storedRaw)) {
$existing = \json_decode($storedRaw, true) ?: [];
}
$encodedSecret = \json_encode([
'clientSecret' => $clientSecret ?? ($existing['clientSecret'] ?? ''),
'fusionAuthDomain' => $endpoint,
]);
$project = $this->persistCredentials($project, $dbForPlatform, $authorization, $clientId, $encodedSecret, $enabled);
// Reuse buildReadResponse to keep PATCH/GET shapes identical and
// guarantee the clientSecret is write-only on every response path.
$response->dynamic($this->buildReadResponse($project), static::getResponseModel());
}
}
@@ -0,0 +1,115 @@
<?php
namespace Appwrite\Platform\Modules\Project\Http\Project\OAuth2;
use Appwrite\Extend\Exception;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\Method;
use Appwrite\SDK\Response as SDKResponse;
use Appwrite\Utopia\Response;
use Utopia\Config\Config;
use Utopia\Database\Document;
use Utopia\Platform\Action;
use Utopia\Platform\Scope\HTTP;
use Utopia\Validator\Text;
class Get extends Action
{
use HTTP;
public static function getName()
{
return 'getProjectOAuth2';
}
public function __construct()
{
$this
->setHttpMethod(Action::HTTP_REQUEST_METHOD_GET)
->setHttpPath('/v1/project/oauth2/:provider')
->desc('Get project OAuth2 provider')
->groups(['api', 'project'])
->label('scope', 'oauth2.read')
->label('sdk', new Method(
namespace: 'project',
group: 'oauth2',
name: 'getOAuth2Provider',
description: <<<EOT
Get a single OAuth2 provider configuration. Credential fields (client secret, p8 file, key/team IDs) are write-only and always returned empty.
EOT,
auth: [AuthType::ADMIN, AuthType::KEY],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
model: [
Response::MODEL_OAUTH2_GITHUB,
Response::MODEL_OAUTH2_DISCORD,
Response::MODEL_OAUTH2_FIGMA,
Response::MODEL_OAUTH2_DROPBOX,
Response::MODEL_OAUTH2_DAILYMOTION,
Response::MODEL_OAUTH2_BITBUCKET,
Response::MODEL_OAUTH2_BITLY,
Response::MODEL_OAUTH2_BOX,
Response::MODEL_OAUTH2_AUTODESK,
Response::MODEL_OAUTH2_GOOGLE,
Response::MODEL_OAUTH2_ZOOM,
Response::MODEL_OAUTH2_ZOHO,
Response::MODEL_OAUTH2_YANDEX,
Response::MODEL_OAUTH2_X,
Response::MODEL_OAUTH2_WORDPRESS,
Response::MODEL_OAUTH2_TWITCH,
Response::MODEL_OAUTH2_STRIPE,
Response::MODEL_OAUTH2_SPOTIFY,
Response::MODEL_OAUTH2_SLACK,
Response::MODEL_OAUTH2_PODIO,
Response::MODEL_OAUTH2_NOTION,
Response::MODEL_OAUTH2_SALESFORCE,
Response::MODEL_OAUTH2_YAHOO,
Response::MODEL_OAUTH2_LINKEDIN,
Response::MODEL_OAUTH2_DISQUS,
Response::MODEL_OAUTH2_AMAZON,
Response::MODEL_OAUTH2_ETSY,
Response::MODEL_OAUTH2_FACEBOOK,
Response::MODEL_OAUTH2_TRADESHIFT,
Response::MODEL_OAUTH2_PAYPAL,
Response::MODEL_OAUTH2_GITLAB,
Response::MODEL_OAUTH2_AUTHENTIK,
Response::MODEL_OAUTH2_AUTH0,
Response::MODEL_OAUTH2_FUSIONAUTH,
Response::MODEL_OAUTH2_KEYCLOAK,
Response::MODEL_OAUTH2_OIDC,
Response::MODEL_OAUTH2_APPLE,
Response::MODEL_OAUTH2_OKTA,
Response::MODEL_OAUTH2_KICK,
Response::MODEL_OAUTH2_MICROSOFT,
],
)
]
))
->param('provider', '', new Text(128), 'OAuth2 provider key. For example: github, google, apple.')
->inject('response')
->inject('project')
->callback($this->action(...));
}
public function action(
string $provider,
Response $response,
Document $project,
): void {
$providers = Config::getParam('oAuthProviders', []);
if (!\array_key_exists($provider, $providers) || !($providers[$provider]['enabled'] ?? false)) {
throw new Exception(Exception::PROJECT_PROVIDER_UNSUPPORTED);
}
$actions = Base::getProviderActions();
if (!isset($actions[$provider])) {
throw new Exception(Exception::PROJECT_PROVIDER_UNSUPPORTED);
}
$updateClass = $actions[$provider];
$action = new $updateClass();
$response->dynamic($action->buildReadResponse($project), $updateClass::getResponseModel());
}
}
@@ -0,0 +1,60 @@
<?php
namespace Appwrite\Platform\Modules\Project\Http\Project\OAuth2\GitHub;
use Appwrite\Auth\OAuth2\Github;
use Appwrite\Platform\Modules\Project\Http\Project\OAuth2\Base;
use Appwrite\Utopia\Response;
class Update extends Base
{
public static function getProviderId(): string
{
return 'github';
}
public static function getProviderClass(): string
{
return Github::class;
}
public static function getProviderLabel(): string
{
return 'GitHub';
}
public static function getProviderSDKMethod(): string
{
return 'updateOAuth2GitHub';
}
public static function getResponseModel(): string
{
return Response::MODEL_OAUTH2_GITHUB;
}
public static function getClientIdName(): string
{
return 'OAuth 2 app Client ID, or App ID';
}
public static function getClientIdExample(): string
{
return 'e4d87900000000540733';
}
public static function getClientIdHint(): string
{
return 'Example of wrong value: 370006';
}
public static function getClientSecretName(): string
{
return 'Client Secret';
}
public static function getClientSecretExample(): string
{
return '5e07c00000000000000000000000000000198bcc';
}
}
@@ -0,0 +1,186 @@
<?php
namespace Appwrite\Platform\Modules\Project\Http\Project\OAuth2\Gitlab;
use Appwrite\Auth\OAuth2\Gitlab;
use Appwrite\Event\Event as QueueEvent;
use Appwrite\Platform\Action;
use Appwrite\Platform\Modules\Project\Http\Project\OAuth2\Base;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\Method;
use Appwrite\SDK\Response as SDKResponse;
use Appwrite\Utopia\Response;
use Utopia\Database\Database;
use Utopia\Database\Document;
use Utopia\Database\Validator\Authorization;
use Utopia\Validator\Boolean;
use Utopia\Validator\Nullable;
use Utopia\Validator\Text;
use Utopia\Validator\URL;
class Update extends Base
{
public static function getProviderId(): string
{
return 'gitlab';
}
public static function getProviderClass(): string
{
return Gitlab::class;
}
public static function getProviderLabel(): string
{
return 'Gitlab';
}
public static function getProviderSDKMethod(): string
{
return 'updateOAuth2Gitlab';
}
public static function getResponseModel(): string
{
return Response::MODEL_OAUTH2_GITLAB;
}
public static function getClientIdParamName(): string
{
return 'applicationId';
}
public static function getClientSecretParamName(): string
{
return 'secret';
}
public static function getClientIdName(): string
{
return 'Application ID';
}
public static function getClientIdExample(): string
{
return 'd41ffe0000000000000000000000000000000000000000000000000000d5e252';
}
public static function getClientSecretName(): string
{
return 'Secret';
}
public static function getClientSecretExample(): string
{
return 'gloas-838cfa0000000000000000000000000000000000000000000000000000ecbb38';
}
public static function getParameters(): array
{
return \array_merge(parent::getParameters(), [
[
'$id' => 'endpoint',
'name' => 'Endpoint',
'example' => 'https://gitlab.com',
'hint' => '',
],
]);
}
public function __construct()
{
$providerId = static::getProviderId();
$providerLabel = static::getProviderLabel();
$this
->setHttpMethod(Action::HTTP_REQUEST_METHOD_PATCH)
->setHttpPath('/v1/project/oauth2/' . $providerId)
->desc('Update project OAuth2 ' . $providerLabel)
->groups(['api', 'project'])
->label('scope', 'oauth2.write')
->label('event', 'oauth2.[providerId].update')
->label('audits.event', 'project.oauth2.[providerId].update')
->label('audits.resource', 'project.oauth2/{response.$id}')
->label('sdk', new Method(
namespace: 'project',
group: 'oauth2',
name: static::getProviderSDKMethod(),
description: 'Update the project OAuth2 ' . $providerLabel . ' configuration.',
auth: [AuthType::ADMIN, AuthType::KEY],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
model: static::getResponseModel(),
)
],
))
->param(static::getClientIdParamName(), null, new Nullable(new Text(256, 0)), static::getClientIdDescription(), optional: true)
->param(static::getClientSecretParamName(), null, new Nullable(new Text(512, 0)), static::getClientSecretDescription(), optional: true)
->param('endpoint', null, new Nullable(new URL(allowEmpty: true)), 'Endpoint URL of self-hosted GitLab instance. For example: https://gitlab.com', optional: true)
->param('enabled', null, new Nullable(new Boolean()), 'OAuth2 sign-in method status. Set to true to enable new session creation. Setting to true will trigger end-to-end credentials validation, and will throw if the credentials are invalid.', true)
->inject('response')
->inject('dbForPlatform')
->inject('project')
->inject('authorization')
->inject('queueForEvents')
->callback($this->handle(...));
}
public function buildReadResponse(Document $project): Document
{
$providerId = static::getProviderId();
$oAuthProviders = $project->getAttribute('oAuthProviders', []);
$decoded = $this->decodeStoredSecret($project);
return new Document([
'$id' => $providerId,
'enabled' => $oAuthProviders[$providerId . 'Enabled'] ?? false,
static::getClientIdParamName() => $oAuthProviders[$providerId . 'Appid'] ?? '',
static::getClientSecretParamName() => '',
'endpoint' => $decoded['endpoint'] ?? '',
]);
}
/**
* Custom callback used instead of the parent's `action()` because Gitlab
* takes an additional `endpoint` parameter. The method is named
* differently to avoid an LSP-incompatible override of Base::action().
*/
public function handle(
?string $applicationId,
?string $secret,
?string $endpoint,
?bool $enabled,
Response $response,
Database $dbForPlatform,
Document $project,
Authorization $authorization,
QueueEvent $queueForEvents
): void {
$providerId = static::getProviderId();
$queueForEvents->setParam('providerId', $providerId);
// The secret is stored as JSON `{"clientSecret": "...", "endpoint": "..."}`
// so that the Gitlab OAuth2 adapter can extract the endpoint via getEndpoint().
// Merge the new values with what's already stored so that submitting only
// one of `secret`/`endpoint` leaves the other untouched.
$encodedSecret = null;
if (!\is_null($secret) || !\is_null($endpoint)) {
$storedRaw = $project->getAttribute('oAuthProviders', [])[$providerId . 'Secret'] ?? '';
$existing = [];
if (!empty($storedRaw)) {
$existing = \json_decode($storedRaw, true) ?: [];
}
$encodedSecret = \json_encode([
'clientSecret' => $secret ?? ($existing['clientSecret'] ?? ''),
'endpoint' => $endpoint ?? ($existing['endpoint'] ?? ''),
]);
}
$project = $this->persistCredentials($project, $dbForPlatform, $authorization, $applicationId, $encodedSecret, $enabled);
// Reuse buildReadResponse to keep PATCH/GET shapes identical and
// guarantee the secret is write-only on every response path.
$response->dynamic($this->buildReadResponse($project), static::getResponseModel());
}
}
@@ -0,0 +1,55 @@
<?php
namespace Appwrite\Platform\Modules\Project\Http\Project\OAuth2\Google;
use Appwrite\Auth\OAuth2\Google;
use Appwrite\Platform\Modules\Project\Http\Project\OAuth2\Base;
use Appwrite\Utopia\Response;
class Update extends Base
{
public static function getProviderId(): string
{
return 'google';
}
public static function getProviderClass(): string
{
return Google::class;
}
public static function getProviderLabel(): string
{
return 'Google';
}
public static function getProviderSDKMethod(): string
{
return 'updateOAuth2Google';
}
public static function getResponseModel(): string
{
return Response::MODEL_OAUTH2_GOOGLE;
}
public static function getClientIdName(): string
{
return 'Client ID';
}
public static function getClientIdExample(): string
{
return '120000000095-92ifjb00000000000000000000g7ijfb.apps.googleusercontent.com';
}
public static function getClientSecretName(): string
{
return 'Client Secret';
}
public static function getClientSecretExample(): string
{
return 'GOCSPX-2k8gsR0000000000000000VNahJj';
}
}
@@ -0,0 +1,183 @@
<?php
namespace Appwrite\Platform\Modules\Project\Http\Project\OAuth2\Keycloak;
use Appwrite\Auth\OAuth2\Keycloak;
use Appwrite\Event\Event as QueueEvent;
use Appwrite\Platform\Action;
use Appwrite\Platform\Modules\Project\Http\Project\OAuth2\Base;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\Method;
use Appwrite\SDK\Response as SDKResponse;
use Appwrite\Utopia\Response;
use Utopia\Database\Database;
use Utopia\Database\Document;
use Utopia\Database\Validator\Authorization;
use Utopia\Validator\Boolean;
use Utopia\Validator\Nullable;
use Utopia\Validator\Text;
class Update extends Base
{
public static function getProviderId(): string
{
return 'keycloak';
}
public static function getProviderClass(): string
{
return Keycloak::class;
}
public static function getProviderLabel(): string
{
return 'Keycloak';
}
public static function getProviderSDKMethod(): string
{
return 'updateOAuth2Keycloak';
}
public static function getResponseModel(): string
{
return Response::MODEL_OAUTH2_KEYCLOAK;
}
public static function getClientIdName(): string
{
return 'Client ID';
}
public static function getClientIdExample(): string
{
return 'appwrite-o0000000st-app';
}
public static function getClientSecretName(): string
{
return 'Client Secret';
}
public static function getClientSecretExample(): string
{
return 'jdjrJd00000000000000000000HUsaZO';
}
public static function getParameters(): array
{
return \array_merge(parent::getParameters(), [
[
'$id' => 'endpoint',
'name' => 'Domain',
'example' => 'keycloak.example.com',
'hint' => '',
],
[
'$id' => 'realmName',
'name' => 'Realm name',
'example' => 'appwrite-realm',
'hint' => '',
],
]);
}
public function __construct()
{
$providerId = static::getProviderId();
$providerLabel = static::getProviderLabel();
$this
->setHttpMethod(Action::HTTP_REQUEST_METHOD_PATCH)
->setHttpPath('/v1/project/oauth2/' . $providerId)
->desc('Update project OAuth2 ' . $providerLabel)
->groups(['api', 'project'])
->label('scope', 'oauth2.write')
->label('event', 'oauth2.[providerId].update')
->label('audits.event', 'project.oauth2.[providerId].update')
->label('audits.resource', 'project.oauth2/{response.$id}')
->label('sdk', new Method(
namespace: 'project',
group: 'oauth2',
name: static::getProviderSDKMethod(),
description: 'Update the project OAuth2 ' . $providerLabel . ' configuration.',
auth: [AuthType::ADMIN, AuthType::KEY],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
model: static::getResponseModel(),
)
],
))
->param(static::getClientIdParamName(), null, new Nullable(new Text(256, 0)), static::getClientIdDescription(), optional: true)
->param(static::getClientSecretParamName(), null, new Nullable(new Text(512, 0)), static::getClientSecretDescription(), optional: true)
->param('endpoint', '', new Text(256, 1), 'Domain of Keycloak instance. For example: keycloak.example.com', optional: false)
->param('realmName', '', new Text(256, 1), 'Keycloak realm name. For example: appwrite-realm', optional: false)
->param('enabled', null, new Nullable(new Boolean()), 'OAuth2 sign-in method status. Set to true to enable new session creation. Setting to true will trigger end-to-end credentials validation, and will throw if the credentials are invalid.', true)
->inject('response')
->inject('dbForPlatform')
->inject('project')
->inject('authorization')
->inject('queueForEvents')
->callback($this->handle(...));
}
public function buildReadResponse(Document $project): Document
{
$providerId = static::getProviderId();
$oAuthProviders = $project->getAttribute('oAuthProviders', []);
$decoded = $this->decodeStoredSecret($project);
return new Document([
'$id' => $providerId,
'enabled' => $oAuthProviders[$providerId . 'Enabled'] ?? false,
static::getClientIdParamName() => $oAuthProviders[$providerId . 'Appid'] ?? '',
static::getClientSecretParamName() => '',
'endpoint' => $decoded['keycloakDomain'] ?? '',
'realmName' => $decoded['keycloakRealm'] ?? '',
]);
}
/**
* Custom callback used instead of the parent's `action()` because Keycloak
* takes additional required `endpoint` and `realmName` parameters. The
* method is named differently to avoid an LSP-incompatible override of
* Base::action().
*/
public function handle(
?string $clientId,
?string $clientSecret,
string $endpoint,
string $realmName,
?bool $enabled,
Response $response,
Database $dbForPlatform,
Document $project,
Authorization $authorization,
QueueEvent $queueForEvents
): void {
$providerId = static::getProviderId();
$queueForEvents->setParam('providerId', $providerId);
// The secret is stored as JSON `{"clientSecret": "...", "keycloakDomain": "...", "keycloakRealm": "..."}`
// to match the shape Keycloak's OAuth2 adapter expects (getKeycloakDomain(), getKeycloakRealm()).
// The `endpoint` and `realmName` params are required on every call, so they're always written.
// `clientSecret` is optional; if omitted, the existing stored secret is preserved.
$storedRaw = $project->getAttribute('oAuthProviders', [])[$providerId . 'Secret'] ?? '';
$existing = [];
if (!empty($storedRaw)) {
$existing = \json_decode($storedRaw, true) ?: [];
}
$encodedSecret = \json_encode([
'clientSecret' => $clientSecret ?? ($existing['clientSecret'] ?? ''),
'keycloakDomain' => $endpoint,
'keycloakRealm' => $realmName,
]);
$project = $this->persistCredentials($project, $dbForPlatform, $authorization, $clientId, $encodedSecret, $enabled);
// Reuse buildReadResponse to keep PATCH/GET shapes identical and
// guarantee the clientSecret is write-only on every response path.
$response->dynamic($this->buildReadResponse($project), static::getResponseModel());
}
}
@@ -0,0 +1,55 @@
<?php
namespace Appwrite\Platform\Modules\Project\Http\Project\OAuth2\Kick;
use Appwrite\Auth\OAuth2\Kick;
use Appwrite\Platform\Modules\Project\Http\Project\OAuth2\Base;
use Appwrite\Utopia\Response;
class Update extends Base
{
public static function getProviderId(): string
{
return 'kick';
}
public static function getProviderClass(): string
{
return Kick::class;
}
public static function getProviderLabel(): string
{
return 'Kick';
}
public static function getProviderSDKMethod(): string
{
return 'updateOAuth2Kick';
}
public static function getResponseModel(): string
{
return Response::MODEL_OAUTH2_KICK;
}
public static function getClientIdName(): string
{
return 'Client ID';
}
public static function getClientIdExample(): string
{
return '01KQ7C00000000000001MFHS32';
}
public static function getClientSecretName(): string
{
return 'Client Secret';
}
public static function getClientSecretExample(): string
{
return '34ac5600000000000000000000000000000000000000000000000000e830c8b';
}
}
@@ -0,0 +1,60 @@
<?php
namespace Appwrite\Platform\Modules\Project\Http\Project\OAuth2\Linkedin;
use Appwrite\Auth\OAuth2\Linkedin;
use Appwrite\Platform\Modules\Project\Http\Project\OAuth2\Base;
use Appwrite\Utopia\Response;
class Update extends Base
{
public static function getProviderId(): string
{
return 'linkedin';
}
public static function getProviderClass(): string
{
return Linkedin::class;
}
public static function getProviderLabel(): string
{
return 'Linkedin';
}
public static function getProviderSDKMethod(): string
{
return 'updateOAuth2Linkedin';
}
public static function getResponseModel(): string
{
return Response::MODEL_OAUTH2_LINKEDIN;
}
public static function getClientSecretParamName(): string
{
return 'primaryClientSecret';
}
public static function getClientIdName(): string
{
return 'Client ID';
}
public static function getClientIdExample(): string
{
return '770000000000dv';
}
public static function getClientSecretName(): string
{
return 'Primary Client Secret or Secondary Client Secret';
}
public static function getClientSecretExample(): string
{
return 'WPL_AP1.2Bf0000000000000./HtlYw==';
}
}
@@ -0,0 +1,182 @@
<?php
namespace Appwrite\Platform\Modules\Project\Http\Project\OAuth2\Microsoft;
use Appwrite\Auth\OAuth2\Microsoft;
use Appwrite\Event\Event as QueueEvent;
use Appwrite\Platform\Action;
use Appwrite\Platform\Modules\Project\Http\Project\OAuth2\Base;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\Method;
use Appwrite\SDK\Response as SDKResponse;
use Appwrite\Utopia\Response;
use Utopia\Database\Database;
use Utopia\Database\Document;
use Utopia\Database\Validator\Authorization;
use Utopia\Validator\Boolean;
use Utopia\Validator\Nullable;
use Utopia\Validator\Text;
class Update extends Base
{
public static function getProviderId(): string
{
return 'microsoft';
}
public static function getProviderClass(): string
{
return Microsoft::class;
}
public static function getProviderLabel(): string
{
return 'Microsoft';
}
public static function getProviderSDKMethod(): string
{
return 'updateOAuth2Microsoft';
}
public static function getResponseModel(): string
{
return Response::MODEL_OAUTH2_MICROSOFT;
}
public static function getClientIdParamName(): string
{
return 'applicationId';
}
public static function getClientSecretParamName(): string
{
return 'applicationSecret';
}
public static function getClientIdName(): string
{
return 'Entra ID Application ID, also known as Client ID';
}
public static function getClientIdExample(): string
{
return '00001111-aaaa-2222-bbbb-3333cccc4444';
}
public static function getClientSecretName(): string
{
return 'Entra ID Application Secret, also known as Client Secret';
}
public static function getClientSecretExample(): string
{
return 'A1bC2dE3fH4iJ5kL6mN7oP8qR9sT0u';
}
public static function getParameters(): array
{
return \array_merge(parent::getParameters(), [
[
'$id' => 'tenant',
'name' => 'Tenant',
'example' => 'common',
'hint' => '',
],
]);
}
public function __construct()
{
$providerId = static::getProviderId();
$providerLabel = static::getProviderLabel();
$this
->setHttpMethod(Action::HTTP_REQUEST_METHOD_PATCH)
->setHttpPath('/v1/project/oauth2/' . $providerId)
->desc('Update project OAuth2 ' . $providerLabel)
->groups(['api', 'project'])
->label('scope', 'oauth2.write')
->label('event', 'oauth2.[providerId].update')
->label('audits.event', 'project.oauth2.[providerId].update')
->label('audits.resource', 'project.oauth2/{response.$id}')
->label('sdk', new Method(
namespace: 'project',
group: 'oauth2',
name: static::getProviderSDKMethod(),
description: 'Update the project OAuth2 ' . $providerLabel . ' configuration.',
auth: [AuthType::ADMIN, AuthType::KEY],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
model: static::getResponseModel(),
)
],
))
->param(static::getClientIdParamName(), null, new Nullable(new Text(256, 0)), static::getClientIdDescription(), optional: true)
->param(static::getClientSecretParamName(), null, new Nullable(new Text(512, 0)), static::getClientSecretDescription(), optional: true)
->param('tenant', '', new Text(256, 1), 'Microsoft Entra ID tenant identifier. Use \'common\', \'organizations\', \'consumers\' or a specific tenant ID. For example: common', optional: false)
->param('enabled', null, new Nullable(new Boolean()), 'OAuth2 sign-in method status. Set to true to enable new session creation. Setting to true will trigger end-to-end credentials validation, and will throw if the credentials are invalid.', true)
->inject('response')
->inject('dbForPlatform')
->inject('project')
->inject('authorization')
->inject('queueForEvents')
->callback($this->handle(...));
}
public function buildReadResponse(Document $project): Document
{
$providerId = static::getProviderId();
$oAuthProviders = $project->getAttribute('oAuthProviders', []);
$decoded = $this->decodeStoredSecret($project);
return new Document([
'$id' => $providerId,
'enabled' => $oAuthProviders[$providerId . 'Enabled'] ?? false,
static::getClientIdParamName() => $oAuthProviders[$providerId . 'Appid'] ?? '',
static::getClientSecretParamName() => '',
'tenant' => $decoded['tenantID'] ?? '',
]);
}
/**
* Custom callback used instead of the parent's `action()` because Microsoft
* takes an additional required `tenant` parameter. The method is named
* differently to avoid an LSP-incompatible override of Base::action().
*/
public function handle(
?string $applicationId,
?string $applicationSecret,
string $tenant,
?bool $enabled,
Response $response,
Database $dbForPlatform,
Document $project,
Authorization $authorization,
QueueEvent $queueForEvents
): void {
$providerId = static::getProviderId();
$queueForEvents->setParam('providerId', $providerId);
// The secret is stored as JSON `{"clientSecret": "...", "tenantID": "..."}`
// to match the shape Microsoft's OAuth2 adapter expects (getTenantID()).
// The `tenant` param is required on every call, so it's always written.
// `applicationSecret` is optional; if omitted, the existing stored secret is preserved.
$storedRaw = $project->getAttribute('oAuthProviders', [])[$providerId . 'Secret'] ?? '';
$existing = [];
if (!empty($storedRaw)) {
$existing = \json_decode($storedRaw, true) ?: [];
}
$encodedSecret = \json_encode([
'clientSecret' => $applicationSecret ?? ($existing['clientSecret'] ?? ''),
'tenantID' => $tenant,
]);
$project = $this->persistCredentials($project, $dbForPlatform, $authorization, $applicationId, $encodedSecret, $enabled);
// Reuse buildReadResponse to keep PATCH/GET shapes identical and
// guarantee the applicationSecret is write-only on every response path.
$response->dynamic($this->buildReadResponse($project), static::getResponseModel());
}
}
@@ -0,0 +1,65 @@
<?php
namespace Appwrite\Platform\Modules\Project\Http\Project\OAuth2\Notion;
use Appwrite\Auth\OAuth2\Notion;
use Appwrite\Platform\Modules\Project\Http\Project\OAuth2\Base;
use Appwrite\Utopia\Response;
class Update extends Base
{
public static function getProviderId(): string
{
return 'notion';
}
public static function getProviderClass(): string
{
return Notion::class;
}
public static function getProviderLabel(): string
{
return 'Notion';
}
public static function getProviderSDKMethod(): string
{
return 'updateOAuth2Notion';
}
public static function getResponseModel(): string
{
return Response::MODEL_OAUTH2_NOTION;
}
public static function getClientIdParamName(): string
{
return 'oauthClientId';
}
public static function getClientSecretParamName(): string
{
return 'oauthClientSecret';
}
public static function getClientIdName(): string
{
return 'OAuth Client ID';
}
public static function getClientIdExample(): string
{
return '341d8700-0000-0000-0000-000000446ee3';
}
public static function getClientSecretName(): string
{
return 'OAuth Client Secret';
}
public static function getClientSecretExample(): string
{
return 'secret_dLUr4b000000000000000000000000000000lFHAa9';
}
}
@@ -0,0 +1,230 @@
<?php
namespace Appwrite\Platform\Modules\Project\Http\Project\OAuth2\Oidc;
use Appwrite\Auth\OAuth2\Oidc;
use Appwrite\Event\Event as QueueEvent;
use Appwrite\Extend\Exception;
use Appwrite\Platform\Action;
use Appwrite\Platform\Modules\Project\Http\Project\OAuth2\Base;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\Method;
use Appwrite\SDK\Response as SDKResponse;
use Appwrite\Utopia\Response;
use Utopia\Database\Database;
use Utopia\Database\Document;
use Utopia\Database\Validator\Authorization;
use Utopia\Validator\Boolean;
use Utopia\Validator\Nullable;
use Utopia\Validator\Text;
use Utopia\Validator\URL;
class Update extends Base
{
public static function getProviderId(): string
{
return 'oidc';
}
public static function getProviderClass(): string
{
return Oidc::class;
}
public static function getProviderLabel(): string
{
return 'Oidc';
}
public static function getProviderSDKMethod(): string
{
return 'updateOAuth2Oidc';
}
public static function getResponseModel(): string
{
return Response::MODEL_OAUTH2_OIDC;
}
public static function getClientIdName(): string
{
return 'Client ID';
}
public static function getClientIdExample(): string
{
return 'qibI2x0000000000000000000000000006L2YFoG';
}
public static function getClientSecretName(): string
{
return 'Client Secret';
}
public static function getClientSecretExample(): string
{
return 'Ah68ed000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003qpcHV';
}
public static function getParameters(): array
{
return \array_merge(parent::getParameters(), [
[
'$id' => 'wellKnownURL',
'name' => 'Well-known URL',
'example' => 'https://myoauth.com/.well-known/openid-configuration',
'hint' => '',
],
[
'$id' => 'authorizationURL',
'name' => 'Authorization URL',
'example' => 'https://myoauth.com/oauth2/authorize',
'hint' => '',
],
[
'$id' => 'tokenUrl',
'name' => 'Token URL',
'example' => 'https://myoauth.com/oauth2/token',
'hint' => '',
],
[
'$id' => 'userInfoUrl',
'name' => 'User Info URL',
'example' => 'https://myoauth.com/oauth2/userinfo',
'hint' => '',
],
]);
}
public function __construct()
{
$providerId = static::getProviderId();
$providerLabel = static::getProviderLabel();
$this
->setHttpMethod(Action::HTTP_REQUEST_METHOD_PATCH)
->setHttpPath('/v1/project/oauth2/' . $providerId)
->desc('Update project OAuth2 ' . $providerLabel)
->groups(['api', 'project'])
->label('scope', 'oauth2.write')
->label('event', 'oauth2.[providerId].update')
->label('audits.event', 'project.oauth2.[providerId].update')
->label('audits.resource', 'project.oauth2/{response.$id}')
->label('sdk', new Method(
namespace: 'project',
group: 'oauth2',
name: static::getProviderSDKMethod(),
description: 'Update the project OAuth2 ' . $providerLabel . ' configuration.',
auth: [AuthType::ADMIN, AuthType::KEY],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
model: static::getResponseModel(),
)
],
))
->param(static::getClientIdParamName(), null, new Nullable(new Text(256, 0)), static::getClientIdDescription(), optional: true)
->param(static::getClientSecretParamName(), null, new Nullable(new Text(512, 0)), static::getClientSecretDescription(), optional: true)
->param('wellKnownURL', null, new Nullable(new URL(allowEmpty: true)), 'OpenID Connect well-known configuration URL. When provided, authorization, token, and user info endpoints can be discovered automatically. For example: https://myoauth.com/.well-known/openid-configuration', optional: true)
->param('authorizationURL', null, new Nullable(new URL(allowEmpty: true)), 'OpenID Connect authorization endpoint URL. Required when wellKnownURL is not provided. For example: https://myoauth.com/oauth2/authorize', optional: true)
->param('tokenUrl', null, new Nullable(new URL(allowEmpty: true)), 'OpenID Connect token endpoint URL. Required when wellKnownURL is not provided. For example: https://myoauth.com/oauth2/token', optional: true)
->param('userInfoUrl', null, new Nullable(new URL(allowEmpty: true)), 'OpenID Connect user info endpoint URL. Required when wellKnownURL is not provided. For example: https://myoauth.com/oauth2/userinfo', optional: true)
->param('enabled', null, new Nullable(new Boolean()), 'OAuth2 sign-in method status. Set to true to enable new session creation. Setting to true will trigger end-to-end credentials validation, and will throw if the credentials are invalid.', true)
->inject('response')
->inject('dbForPlatform')
->inject('project')
->inject('authorization')
->inject('queueForEvents')
->callback($this->handle(...));
}
public function buildReadResponse(Document $project): Document
{
$providerId = static::getProviderId();
$oAuthProviders = $project->getAttribute('oAuthProviders', []);
$decoded = $this->decodeStoredSecret($project);
return new Document([
'$id' => $providerId,
'enabled' => $oAuthProviders[$providerId . 'Enabled'] ?? false,
static::getClientIdParamName() => $oAuthProviders[$providerId . 'Appid'] ?? '',
static::getClientSecretParamName() => '',
'wellKnownURL' => $decoded['wellKnownEndpoint'] ?? '',
'authorizationURL' => $decoded['authorizationEndpoint'] ?? '',
'tokenUrl' => $decoded['tokenEndpoint'] ?? '',
'userInfoUrl' => $decoded['userInfoEndpoint'] ?? '',
]);
}
/**
* Custom callback used instead of the parent's `action()` because OIDC takes
* a well-known URL plus three discovery URLs (authorization, token, user
* info), all stored together with the client secret as JSON. The method is
* named differently to avoid an LSP-incompatible override of Base::action().
*
* Enabling the provider requires either a non-empty `wellKnownEndpoint`,
* or all three of `authorizationEndpoint`, `tokenEndpoint`, and
* `userInfoEndpoint` to be set. The check considers the merged state of
* existing stored values plus the new values from the request, so callers
* can enable the provider in a single request without re-sending fields
* that were configured previously.
*/
public function handle(
?string $clientId,
?string $clientSecret,
?string $wellKnownURL,
?string $authorizationURL,
?string $tokenUrl,
?string $userInfoUrl,
?bool $enabled,
Response $response,
Database $dbForPlatform,
Document $project,
Authorization $authorization,
QueueEvent $queueForEvents
): void {
$providerId = static::getProviderId();
$queueForEvents->setParam('providerId', $providerId);
// The secret is stored as JSON
// `{"clientSecret": "...", "wellKnownEndpoint": "...", "authorizationEndpoint": "...", "tokenEndpoint": "...", "userInfoEndpoint": "..."}`
// so that the OIDC OAuth2 adapter can extract each endpoint individually.
// Merge new values with what's already stored so that submitting only a
// subset of fields leaves the others untouched.
$storedRaw = $project->getAttribute('oAuthProviders', [])[$providerId . 'Secret'] ?? '';
$existing = [];
if (!empty($storedRaw)) {
$existing = \json_decode($storedRaw, true) ?: [];
}
$merged = [
'clientSecret' => $clientSecret ?? ($existing['clientSecret'] ?? ''),
'wellKnownEndpoint' => $wellKnownURL ?? ($existing['wellKnownEndpoint'] ?? ''),
'authorizationEndpoint' => $authorizationURL ?? ($existing['authorizationEndpoint'] ?? ''),
'tokenEndpoint' => $tokenUrl ?? ($existing['tokenEndpoint'] ?? ''),
'userInfoEndpoint' => $userInfoUrl ?? ($existing['userInfoEndpoint'] ?? ''),
];
// When enabling, require either wellKnownEndpoint alone, or all three
// discovery URLs (authorization, token, user info). Skip this check
// when disabling or when leaving the enabled flag unchanged.
if ($enabled === true) {
$hasWellKnown = !empty($merged['wellKnownEndpoint']);
$hasAllDiscovery = !empty($merged['authorizationEndpoint'])
&& !empty($merged['tokenEndpoint'])
&& !empty($merged['userInfoEndpoint']);
if (!$hasWellKnown && !$hasAllDiscovery) {
throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'Enabling OpenID Connect requires either wellKnownURL, or all of authorizationURL, tokenUrl, and userInfoUrl.');
}
}
$encodedSecret = \json_encode($merged);
$project = $this->persistCredentials($project, $dbForPlatform, $authorization, $clientId, $encodedSecret, $enabled);
// Reuse buildReadResponse to keep PATCH/GET shapes identical and
// guarantee the clientSecret is write-only on every response path.
$response->dynamic($this->buildReadResponse($project), static::getResponseModel());
}
}
@@ -0,0 +1,198 @@
<?php
namespace Appwrite\Platform\Modules\Project\Http\Project\OAuth2\Okta;
use Appwrite\Auth\OAuth2\Okta;
use Appwrite\Event\Event as QueueEvent;
use Appwrite\Extend\Exception;
use Appwrite\Platform\Action;
use Appwrite\Platform\Modules\Project\Http\Project\OAuth2\Base;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\Method;
use Appwrite\SDK\Response as SDKResponse;
use Appwrite\Utopia\Response;
use Utopia\Database\Database;
use Utopia\Database\Document;
use Utopia\Database\Validator\Authorization;
use Utopia\Validator\Boolean;
use Utopia\Validator\Domain as ValidatorDomain;
use Utopia\Validator\Nullable;
use Utopia\Validator\Text;
class Update extends Base
{
public static function getProviderId(): string
{
return 'okta';
}
public static function getProviderClass(): string
{
return Okta::class;
}
public static function getProviderLabel(): string
{
return 'Okta';
}
public static function getProviderSDKMethod(): string
{
return 'updateOAuth2Okta';
}
public static function getResponseModel(): string
{
return Response::MODEL_OAUTH2_OKTA;
}
public static function getClientIdName(): string
{
return 'Client ID';
}
public static function getClientIdExample(): string
{
return '0oa00000000000000698';
}
public static function getClientSecretName(): string
{
return 'Client Secret';
}
public static function getClientSecretExample(): string
{
return 'Kiq0000000000000000000000000000000000000-00000000000H2L5-3SJ-vRV';
}
public static function getParameters(): array
{
return \array_merge(parent::getParameters(), [
[
'$id' => 'domain',
'name' => 'Domain',
'example' => 'trial-6400025.okta.com',
'hint' => 'Example of wrong value: trial-6400025-admin.okta.com, or https://trial-6400025.okta.com/',
],
[
'$id' => 'authorizationServerId',
'name' => 'Authorization Server ID',
'example' => 'aus000000000000000h7z',
'hint' => '',
],
]);
}
public function __construct()
{
$providerId = static::getProviderId();
$providerLabel = static::getProviderLabel();
$this
->setHttpMethod(Action::HTTP_REQUEST_METHOD_PATCH)
->setHttpPath('/v1/project/oauth2/' . $providerId)
->desc('Update project OAuth2 ' . $providerLabel)
->groups(['api', 'project'])
->label('scope', 'oauth2.write')
->label('event', 'oauth2.[providerId].update')
->label('audits.event', 'project.oauth2.[providerId].update')
->label('audits.resource', 'project.oauth2/{response.$id}')
->label('sdk', new Method(
namespace: 'project',
group: 'oauth2',
name: static::getProviderSDKMethod(),
description: 'Update the project OAuth2 ' . $providerLabel . ' configuration.',
auth: [AuthType::ADMIN, AuthType::KEY],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
model: static::getResponseModel(),
)
],
))
->param(static::getClientIdParamName(), null, new Nullable(new Text(256, 0)), static::getClientIdDescription(), optional: true)
->param(static::getClientSecretParamName(), null, new Nullable(new Text(512, 0)), static::getClientSecretDescription(), optional: true)
->param('domain', null, new Nullable(new ValidatorDomain(allowEmpty: true)), 'Okta company domain. Required when enabling the provider. For example: trial-6400025.okta.com. Example of wrong value: trial-6400025-admin.okta.com, or https://trial-6400025.okta.com/', optional: true)
->param('authorizationServerId', null, new Nullable(new Text(256, 0)), 'Custom Authorization Servers. Optional, can be left empty or unconfigured. For example: aus000000000000000h7z', optional: true)
->param('enabled', null, new Nullable(new Boolean()), 'OAuth2 sign-in method status. Set to true to enable new session creation. Setting to true will trigger end-to-end credentials validation, and will throw if the credentials are invalid.', true)
->inject('response')
->inject('dbForPlatform')
->inject('project')
->inject('authorization')
->inject('queueForEvents')
->callback($this->handle(...));
}
public function buildReadResponse(Document $project): Document
{
$providerId = static::getProviderId();
$oAuthProviders = $project->getAttribute('oAuthProviders', []);
$decoded = $this->decodeStoredSecret($project);
return new Document([
'$id' => $providerId,
'enabled' => $oAuthProviders[$providerId . 'Enabled'] ?? false,
static::getClientIdParamName() => $oAuthProviders[$providerId . 'Appid'] ?? '',
static::getClientSecretParamName() => '',
'domain' => $decoded['oktaDomain'] ?? '',
'authorizationServerId' => $decoded['authorizationServerId'] ?? '',
]);
}
/**
* Custom callback used instead of the parent's `action()` because Okta
* takes additional optional `domain` and `authorizationServerId` parameters.
* The method is named differently to avoid an LSP-incompatible override of
* Base::action().
*/
public function handle(
?string $clientId,
?string $clientSecret,
?string $domain,
?string $authorizationServerId,
?bool $enabled,
Response $response,
Database $dbForPlatform,
Document $project,
Authorization $authorization,
QueueEvent $queueForEvents
): void {
$providerId = static::getProviderId();
$queueForEvents->setParam('providerId', $providerId);
// The secret is stored as JSON `{"clientSecret": "...", "oktaDomain": "...", "authorizationServerId": "..."}`
// to match the shape Okta's OAuth2 adapter expects.
// Merge new values with existing storage so that submitting only some of
// the parameters leaves the others untouched.
$storedRaw = $project->getAttribute('oAuthProviders', [])[$providerId . 'Secret'] ?? '';
$existing = [];
if (!empty($storedRaw)) {
$existing = \json_decode($storedRaw, true) ?: [];
}
$encodedSecret = null;
if (!\is_null($clientSecret) || !\is_null($domain) || !\is_null($authorizationServerId)) {
$encodedSecret = \json_encode([
'clientSecret' => $clientSecret ?? ($existing['clientSecret'] ?? ''),
'oktaDomain' => $domain ?? ($existing['oktaDomain'] ?? ''),
'authorizationServerId' => $authorizationServerId ?? ($existing['authorizationServerId'] ?? ''),
]);
}
// Domain is required when enabling the provider, since Okta builds its
// authorization, token and userinfo URLs from it.
if ($enabled === true) {
$effectiveDomain = $domain ?? ($existing['oktaDomain'] ?? '');
if (empty($effectiveDomain)) {
throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'Domain is required when enabling Okta OAuth2 provider.');
}
}
$project = $this->persistCredentials($project, $dbForPlatform, $authorization, $clientId, $encodedSecret, $enabled);
// Reuse buildReadResponse to keep PATCH/GET shapes identical and
// guarantee the clientSecret is write-only on every response path.
$response->dynamic($this->buildReadResponse($project), static::getResponseModel());
}
}
@@ -0,0 +1,60 @@
<?php
namespace Appwrite\Platform\Modules\Project\Http\Project\OAuth2\Paypal;
use Appwrite\Auth\OAuth2\Paypal;
use Appwrite\Platform\Modules\Project\Http\Project\OAuth2\Base;
use Appwrite\Utopia\Response;
class Update extends Base
{
public static function getProviderId(): string
{
return 'paypal';
}
public static function getProviderClass(): string
{
return Paypal::class;
}
public static function getProviderLabel(): string
{
return 'Paypal';
}
public static function getProviderSDKMethod(): string
{
return 'updateOAuth2Paypal';
}
public static function getResponseModel(): string
{
return Response::MODEL_OAUTH2_PAYPAL;
}
public static function getClientSecretParamName(): string
{
return 'secretKey';
}
public static function getClientIdName(): string
{
return 'Client ID';
}
public static function getClientIdExample(): string
{
return 'AdhIEG7-000000000000-0000000000000000000000000000000-0000000000000000000000-2pyB';
}
public static function getClientSecretName(): string
{
return 'Secret Key 1 or Secret Key 2';
}
public static function getClientSecretExample(): string
{
return 'EH8KCXtew--000000000000000000000000000000000000000_C-1_5UP_000000000000000CB7KDp';
}
}
@@ -0,0 +1,29 @@
<?php
namespace Appwrite\Platform\Modules\Project\Http\Project\OAuth2\PaypalSandbox;
use Appwrite\Auth\OAuth2\PaypalSandbox;
use Appwrite\Platform\Modules\Project\Http\Project\OAuth2\Paypal\Update as PaypalUpdate;
class Update extends PaypalUpdate
{
public static function getProviderId(): string
{
return 'paypalSandbox';
}
public static function getProviderClass(): string
{
return PaypalSandbox::class;
}
public static function getProviderLabel(): string
{
return 'PaypalSandbox';
}
public static function getProviderSDKMethod(): string
{
return 'updateOAuth2PaypalSandbox';
}
}
@@ -0,0 +1,55 @@
<?php
namespace Appwrite\Platform\Modules\Project\Http\Project\OAuth2\Podio;
use Appwrite\Auth\OAuth2\Podio;
use Appwrite\Platform\Modules\Project\Http\Project\OAuth2\Base;
use Appwrite\Utopia\Response;
class Update extends Base
{
public static function getProviderId(): string
{
return 'podio';
}
public static function getProviderClass(): string
{
return Podio::class;
}
public static function getProviderLabel(): string
{
return 'Podio';
}
public static function getProviderSDKMethod(): string
{
return 'updateOAuth2Podio';
}
public static function getResponseModel(): string
{
return Response::MODEL_OAUTH2_PODIO;
}
public static function getClientIdName(): string
{
return 'Client ID';
}
public static function getClientIdExample(): string
{
return 'appwrite-o0000000st-app';
}
public static function getClientSecretName(): string
{
return 'Client Secret';
}
public static function getClientSecretExample(): string
{
return 'Rn247T0000000000000000000000000000000000000000000000000000W2zWTN';
}
}
@@ -0,0 +1,65 @@
<?php
namespace Appwrite\Platform\Modules\Project\Http\Project\OAuth2\Salesforce;
use Appwrite\Auth\OAuth2\Salesforce;
use Appwrite\Platform\Modules\Project\Http\Project\OAuth2\Base;
use Appwrite\Utopia\Response;
class Update extends Base
{
public static function getProviderId(): string
{
return 'salesforce';
}
public static function getProviderClass(): string
{
return Salesforce::class;
}
public static function getProviderLabel(): string
{
return 'Salesforce';
}
public static function getProviderSDKMethod(): string
{
return 'updateOAuth2Salesforce';
}
public static function getResponseModel(): string
{
return Response::MODEL_OAUTH2_SALESFORCE;
}
public static function getClientIdParamName(): string
{
return 'customerKey';
}
public static function getClientSecretParamName(): string
{
return 'customerSecret';
}
public static function getClientIdName(): string
{
return 'Consumer Key';
}
public static function getClientIdExample(): string
{
return '3MVG9I0000000000000000000000000000000000000000000000000000000000000000000000000C5Aejq';
}
public static function getClientSecretName(): string
{
return 'Consumer Secret';
}
public static function getClientSecretExample(): string
{
return '3w000000000000e2';
}
}
@@ -0,0 +1,55 @@
<?php
namespace Appwrite\Platform\Modules\Project\Http\Project\OAuth2\Slack;
use Appwrite\Auth\OAuth2\Slack;
use Appwrite\Platform\Modules\Project\Http\Project\OAuth2\Base;
use Appwrite\Utopia\Response;
class Update extends Base
{
public static function getProviderId(): string
{
return 'slack';
}
public static function getProviderClass(): string
{
return Slack::class;
}
public static function getProviderLabel(): string
{
return 'Slack';
}
public static function getProviderSDKMethod(): string
{
return 'updateOAuth2Slack';
}
public static function getResponseModel(): string
{
return Response::MODEL_OAUTH2_SLACK;
}
public static function getClientIdName(): string
{
return 'Client ID';
}
public static function getClientIdExample(): string
{
return '23000000089.15000000000023';
}
public static function getClientSecretName(): string
{
return 'Client Secret';
}
public static function getClientSecretExample(): string
{
return '81656000000000000000000000f3d2fd';
}
}
@@ -0,0 +1,55 @@
<?php
namespace Appwrite\Platform\Modules\Project\Http\Project\OAuth2\Spotify;
use Appwrite\Auth\OAuth2\Spotify;
use Appwrite\Platform\Modules\Project\Http\Project\OAuth2\Base;
use Appwrite\Utopia\Response;
class Update extends Base
{
public static function getProviderId(): string
{
return 'spotify';
}
public static function getProviderClass(): string
{
return Spotify::class;
}
public static function getProviderLabel(): string
{
return 'Spotify';
}
public static function getProviderSDKMethod(): string
{
return 'updateOAuth2Spotify';
}
public static function getResponseModel(): string
{
return Response::MODEL_OAUTH2_SPOTIFY;
}
public static function getClientIdName(): string
{
return 'Client ID';
}
public static function getClientIdExample(): string
{
return '6ec271000000000000000000009beace';
}
public static function getClientSecretName(): string
{
return 'Client Secret';
}
public static function getClientSecretExample(): string
{
return 'db068a000000000000000000008b5b9f';
}
}
@@ -0,0 +1,60 @@
<?php
namespace Appwrite\Platform\Modules\Project\Http\Project\OAuth2\Stripe;
use Appwrite\Auth\OAuth2\Stripe;
use Appwrite\Platform\Modules\Project\Http\Project\OAuth2\Base;
use Appwrite\Utopia\Response;
class Update extends Base
{
public static function getProviderId(): string
{
return 'stripe';
}
public static function getProviderClass(): string
{
return Stripe::class;
}
public static function getProviderLabel(): string
{
return 'Stripe';
}
public static function getProviderSDKMethod(): string
{
return 'updateOAuth2Stripe';
}
public static function getResponseModel(): string
{
return Response::MODEL_OAUTH2_STRIPE;
}
public static function getClientSecretParamName(): string
{
return 'apiSecretKey';
}
public static function getClientIdName(): string
{
return 'Client ID';
}
public static function getClientIdExample(): string
{
return 'ca_UKibXX0000000000000000000006byvR';
}
public static function getClientSecretName(): string
{
return 'API Secret Key';
}
public static function getClientSecretExample(): string
{
return 'sk_51SfOd000000000000000000000000000000000000000000000000000000000000000000000000000000000000000QGWYfp';
}
}
@@ -0,0 +1,65 @@
<?php
namespace Appwrite\Platform\Modules\Project\Http\Project\OAuth2\Tradeshift;
use Appwrite\Auth\OAuth2\Tradeshift;
use Appwrite\Platform\Modules\Project\Http\Project\OAuth2\Base;
use Appwrite\Utopia\Response;
class Update extends Base
{
public static function getProviderId(): string
{
return 'tradeshift';
}
public static function getProviderClass(): string
{
return Tradeshift::class;
}
public static function getProviderLabel(): string
{
return 'Tradeshift';
}
public static function getProviderSDKMethod(): string
{
return 'updateOAuth2Tradeshift';
}
public static function getResponseModel(): string
{
return Response::MODEL_OAUTH2_TRADESHIFT;
}
public static function getClientIdParamName(): string
{
return 'oauth2ClientId';
}
public static function getClientSecretParamName(): string
{
return 'oauth2ClientSecret';
}
public static function getClientIdName(): string
{
return 'OAuth2 Client ID';
}
public static function getClientIdExample(): string
{
return 'appwrite-tes00000.0000000000est-app';
}
public static function getClientSecretName(): string
{
return 'OAuth2 Client Secret';
}
public static function getClientSecretExample(): string
{
return '7cb52700-0000-0000-0000-000000ca5b83';
}
}
@@ -0,0 +1,29 @@
<?php
namespace Appwrite\Platform\Modules\Project\Http\Project\OAuth2\TradeshiftSandbox;
use Appwrite\Auth\OAuth2\TradeshiftBox;
use Appwrite\Platform\Modules\Project\Http\Project\OAuth2\Tradeshift\Update as TradeshiftUpdate;
class Update extends TradeshiftUpdate
{
public static function getProviderId(): string
{
return 'tradeshiftBox';
}
public static function getProviderClass(): string
{
return TradeshiftBox::class;
}
public static function getProviderLabel(): string
{
return 'Tradeshift Sandbox';
}
public static function getProviderSDKMethod(): string
{
return 'updateOAuth2TradeshiftSandbox';
}
}
@@ -0,0 +1,55 @@
<?php
namespace Appwrite\Platform\Modules\Project\Http\Project\OAuth2\Twitch;
use Appwrite\Auth\OAuth2\Twitch;
use Appwrite\Platform\Modules\Project\Http\Project\OAuth2\Base;
use Appwrite\Utopia\Response;
class Update extends Base
{
public static function getProviderId(): string
{
return 'twitch';
}
public static function getProviderClass(): string
{
return Twitch::class;
}
public static function getProviderLabel(): string
{
return 'Twitch';
}
public static function getProviderSDKMethod(): string
{
return 'updateOAuth2Twitch';
}
public static function getResponseModel(): string
{
return Response::MODEL_OAUTH2_TWITCH;
}
public static function getClientIdName(): string
{
return 'Client ID';
}
public static function getClientIdExample(): string
{
return 'vvi0in000000000000000000ikmt9p';
}
public static function getClientSecretName(): string
{
return 'Client Secret';
}
public static function getClientSecretExample(): string
{
return 'pmapue000000000000000000zylw3v';
}
}
@@ -0,0 +1,55 @@
<?php
namespace Appwrite\Platform\Modules\Project\Http\Project\OAuth2\WordPress;
use Appwrite\Auth\OAuth2\WordPress;
use Appwrite\Platform\Modules\Project\Http\Project\OAuth2\Base;
use Appwrite\Utopia\Response;
class Update extends Base
{
public static function getProviderId(): string
{
return 'wordpress';
}
public static function getProviderClass(): string
{
return WordPress::class;
}
public static function getProviderLabel(): string
{
return 'WordPress';
}
public static function getProviderSDKMethod(): string
{
return 'updateOAuth2WordPress';
}
public static function getResponseModel(): string
{
return Response::MODEL_OAUTH2_WORDPRESS;
}
public static function getClientIdName(): string
{
return 'Client ID';
}
public static function getClientIdExample(): string
{
return '130005';
}
public static function getClientSecretName(): string
{
return 'Client Secret';
}
public static function getClientSecretExample(): string
{
return 'PlBfJS0000000000000000000000000000000000000000000000000000EdUZJk';
}
}
@@ -0,0 +1,65 @@
<?php
namespace Appwrite\Platform\Modules\Project\Http\Project\OAuth2\X;
use Appwrite\Auth\OAuth2\X;
use Appwrite\Platform\Modules\Project\Http\Project\OAuth2\Base;
use Appwrite\Utopia\Response;
class Update extends Base
{
public static function getProviderId(): string
{
return 'x';
}
public static function getProviderClass(): string
{
return X::class;
}
public static function getProviderLabel(): string
{
return 'X';
}
public static function getProviderSDKMethod(): string
{
return 'updateOAuth2X';
}
public static function getResponseModel(): string
{
return Response::MODEL_OAUTH2_X;
}
public static function getClientIdParamName(): string
{
return 'customerKey';
}
public static function getClientSecretParamName(): string
{
return 'secretKey';
}
public static function getClientIdName(): string
{
return 'Customer Key';
}
public static function getClientIdExample(): string
{
return 'slzZV0000000000000NFLaWT';
}
public static function getClientSecretName(): string
{
return 'Secret Key';
}
public static function getClientSecretExample(): string
{
return 'tkEPkp00000000000000000000000000000000000000FTxbI9';
}
}
@@ -0,0 +1,74 @@
<?php
namespace Appwrite\Platform\Modules\Project\Http\Project\OAuth2;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\Method;
use Appwrite\SDK\Response as SDKResponse;
use Appwrite\Utopia\Response;
use Utopia\Config\Config;
use Utopia\Database\Document;
use Utopia\Platform\Action;
use Utopia\Platform\Scope\HTTP;
class XList extends Action
{
use HTTP;
public static function getName()
{
return 'listProjectOAuth2';
}
public function __construct()
{
$this
->setHttpMethod(Action::HTTP_REQUEST_METHOD_GET)
->setHttpPath('/v1/project/oauth2')
->desc('List project OAuth2 providers')
->groups(['api', 'project'])
->label('scope', 'oauth2.read')
->label('sdk', new Method(
namespace: 'project',
group: 'oauth2',
name: 'listOAuth2Providers',
description: <<<EOT
Get a list of all OAuth2 providers supported by the server, along with the project's configuration for each. Credential fields are write-only and always returned empty.
EOT,
auth: [AuthType::ADMIN, AuthType::KEY],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
model: Response::MODEL_OAUTH2_PROVIDER_LIST,
)
]
))
->inject('response')
->inject('project')
->callback($this->action(...));
}
public function action(
Response $response,
Document $project,
): void {
$providers = Config::getParam('oAuthProviders', []);
$actions = Base::getProviderActions();
$documents = [];
foreach ($actions as $providerId => $updateClass) {
if (!($providers[$providerId]['enabled'] ?? false)) {
// Disabled by Appwrite configuration, exclude from response
continue;
}
$action = new $updateClass();
$documents[] = $action->buildReadResponse($project);
}
$response->dynamic(new Document([
'total' => \count($documents),
'providers' => $documents,
]), Response::MODEL_OAUTH2_PROVIDER_LIST);
}
}
@@ -0,0 +1,55 @@
<?php
namespace Appwrite\Platform\Modules\Project\Http\Project\OAuth2\Yahoo;
use Appwrite\Auth\OAuth2\Yahoo;
use Appwrite\Platform\Modules\Project\Http\Project\OAuth2\Base;
use Appwrite\Utopia\Response;
class Update extends Base
{
public static function getProviderId(): string
{
return 'yahoo';
}
public static function getProviderClass(): string
{
return Yahoo::class;
}
public static function getProviderLabel(): string
{
return 'Yahoo';
}
public static function getProviderSDKMethod(): string
{
return 'updateOAuth2Yahoo';
}
public static function getResponseModel(): string
{
return Response::MODEL_OAUTH2_YAHOO;
}
public static function getClientIdName(): string
{
return 'Client ID, also known as Customer Key';
}
public static function getClientIdExample(): string
{
return 'dj0yJm000000000000000000000000000000000000000000000000000000000000000000000000000000000000Z4PWRm';
}
public static function getClientSecretName(): string
{
return 'Client Secret, also known as Customer Secret';
}
public static function getClientSecretExample(): string
{
return 'cf978f0000000000000000000000000000c5e2e9';
}
}
@@ -0,0 +1,55 @@
<?php
namespace Appwrite\Platform\Modules\Project\Http\Project\OAuth2\Yandex;
use Appwrite\Auth\OAuth2\Yandex;
use Appwrite\Platform\Modules\Project\Http\Project\OAuth2\Base;
use Appwrite\Utopia\Response;
class Update extends Base
{
public static function getProviderId(): string
{
return 'yandex';
}
public static function getProviderClass(): string
{
return Yandex::class;
}
public static function getProviderLabel(): string
{
return 'Yandex';
}
public static function getProviderSDKMethod(): string
{
return 'updateOAuth2Yandex';
}
public static function getResponseModel(): string
{
return Response::MODEL_OAUTH2_YANDEX;
}
public static function getClientIdName(): string
{
return 'Client ID';
}
public static function getClientIdExample(): string
{
return '6a8a6a0000000000000000000091483c';
}
public static function getClientSecretName(): string
{
return 'Client Secret';
}
public static function getClientSecretExample(): string
{
return 'bbf98500000000000000000000c75a63';
}
}
@@ -0,0 +1,55 @@
<?php
namespace Appwrite\Platform\Modules\Project\Http\Project\OAuth2\Zoho;
use Appwrite\Auth\OAuth2\Zoho;
use Appwrite\Platform\Modules\Project\Http\Project\OAuth2\Base;
use Appwrite\Utopia\Response;
class Update extends Base
{
public static function getProviderId(): string
{
return 'zoho';
}
public static function getProviderClass(): string
{
return Zoho::class;
}
public static function getProviderLabel(): string
{
return 'Zoho';
}
public static function getProviderSDKMethod(): string
{
return 'updateOAuth2Zoho';
}
public static function getResponseModel(): string
{
return Response::MODEL_OAUTH2_ZOHO;
}
public static function getClientIdName(): string
{
return 'Client ID';
}
public static function getClientIdExample(): string
{
return '1000.83C178000000000000000000RPNX0B';
}
public static function getClientSecretName(): string
{
return 'Client Secret';
}
public static function getClientSecretExample(): string
{
return 'fb5cac000000000000000000000000000000a68f6e';
}
}
@@ -0,0 +1,55 @@
<?php
namespace Appwrite\Platform\Modules\Project\Http\Project\OAuth2\Zoom;
use Appwrite\Auth\OAuth2\Zoom;
use Appwrite\Platform\Modules\Project\Http\Project\OAuth2\Base;
use Appwrite\Utopia\Response;
class Update extends Base
{
public static function getProviderId(): string
{
return 'zoom';
}
public static function getProviderClass(): string
{
return Zoom::class;
}
public static function getProviderLabel(): string
{
return 'Zoom';
}
public static function getProviderSDKMethod(): string
{
return 'updateOAuth2Zoom';
}
public static function getResponseModel(): string
{
return Response::MODEL_OAUTH2_ZOOM;
}
public static function getClientIdName(): string
{
return 'Client ID';
}
public static function getClientIdExample(): string
{
return 'QMAC00000000000000w0AQ';
}
public static function getClientSecretName(): string
{
return 'Client Secret';
}
public static function getClientSecretExample(): string
{
return 'GAWsG4000000000000000000007U01ON';
}
}
@@ -16,6 +16,50 @@ use Appwrite\Platform\Modules\Project\Http\Project\MockPhone\Delete as DeleteMoc
use Appwrite\Platform\Modules\Project\Http\Project\MockPhone\Get as GetMockPhone;
use Appwrite\Platform\Modules\Project\Http\Project\MockPhone\Update as UpdateMockPhone;
use Appwrite\Platform\Modules\Project\Http\Project\MockPhone\XList as ListMockPhones;
use Appwrite\Platform\Modules\Project\Http\Project\OAuth2\Amazon\Update as UpdateOAuth2Amazon;
use Appwrite\Platform\Modules\Project\Http\Project\OAuth2\Apple\Update as UpdateOAuth2Apple;
use Appwrite\Platform\Modules\Project\Http\Project\OAuth2\Auth0\Update as UpdateOAuth2Auth0;
use Appwrite\Platform\Modules\Project\Http\Project\OAuth2\Authentik\Update as UpdateOAuth2Authentik;
use Appwrite\Platform\Modules\Project\Http\Project\OAuth2\Autodesk\Update as UpdateOAuth2Autodesk;
use Appwrite\Platform\Modules\Project\Http\Project\OAuth2\Bitbucket\Update as UpdateOAuth2Bitbucket;
use Appwrite\Platform\Modules\Project\Http\Project\OAuth2\Bitly\Update as UpdateOAuth2Bitly;
use Appwrite\Platform\Modules\Project\Http\Project\OAuth2\Box\Update as UpdateOAuth2Box;
use Appwrite\Platform\Modules\Project\Http\Project\OAuth2\Dailymotion\Update as UpdateOAuth2Dailymotion;
use Appwrite\Platform\Modules\Project\Http\Project\OAuth2\Discord\Update as UpdateOAuth2Discord;
use Appwrite\Platform\Modules\Project\Http\Project\OAuth2\Disqus\Update as UpdateOAuth2Disqus;
use Appwrite\Platform\Modules\Project\Http\Project\OAuth2\Dropbox\Update as UpdateOAuth2Dropbox;
use Appwrite\Platform\Modules\Project\Http\Project\OAuth2\Etsy\Update as UpdateOAuth2Etsy;
use Appwrite\Platform\Modules\Project\Http\Project\OAuth2\Facebook\Update as UpdateOAuth2Facebook;
use Appwrite\Platform\Modules\Project\Http\Project\OAuth2\Figma\Update as UpdateOAuth2Figma;
use Appwrite\Platform\Modules\Project\Http\Project\OAuth2\FusionAuth\Update as UpdateOAuth2FusionAuth;
use Appwrite\Platform\Modules\Project\Http\Project\OAuth2\Get as GetOAuth2Provider;
use Appwrite\Platform\Modules\Project\Http\Project\OAuth2\GitHub\Update as UpdateOAuth2GitHub;
use Appwrite\Platform\Modules\Project\Http\Project\OAuth2\Gitlab\Update as UpdateOAuth2Gitlab;
use Appwrite\Platform\Modules\Project\Http\Project\OAuth2\Google\Update as UpdateOAuth2Google;
use Appwrite\Platform\Modules\Project\Http\Project\OAuth2\Keycloak\Update as UpdateOAuth2Keycloak;
use Appwrite\Platform\Modules\Project\Http\Project\OAuth2\Kick\Update as UpdateOAuth2Kick;
use Appwrite\Platform\Modules\Project\Http\Project\OAuth2\Linkedin\Update as UpdateOAuth2Linkedin;
use Appwrite\Platform\Modules\Project\Http\Project\OAuth2\Microsoft\Update as UpdateOAuth2Microsoft;
use Appwrite\Platform\Modules\Project\Http\Project\OAuth2\Notion\Update as UpdateOAuth2Notion;
use Appwrite\Platform\Modules\Project\Http\Project\OAuth2\Oidc\Update as UpdateOAuth2Oidc;
use Appwrite\Platform\Modules\Project\Http\Project\OAuth2\Okta\Update as UpdateOAuth2Okta;
use Appwrite\Platform\Modules\Project\Http\Project\OAuth2\Paypal\Update as UpdateOAuth2Paypal;
use Appwrite\Platform\Modules\Project\Http\Project\OAuth2\PaypalSandbox\Update as UpdateOAuth2PaypalSandbox;
use Appwrite\Platform\Modules\Project\Http\Project\OAuth2\Podio\Update as UpdateOAuth2Podio;
use Appwrite\Platform\Modules\Project\Http\Project\OAuth2\Salesforce\Update as UpdateOAuth2Salesforce;
use Appwrite\Platform\Modules\Project\Http\Project\OAuth2\Slack\Update as UpdateOAuth2Slack;
use Appwrite\Platform\Modules\Project\Http\Project\OAuth2\Spotify\Update as UpdateOAuth2Spotify;
use Appwrite\Platform\Modules\Project\Http\Project\OAuth2\Stripe\Update as UpdateOAuth2Stripe;
use Appwrite\Platform\Modules\Project\Http\Project\OAuth2\Tradeshift\Update as UpdateOAuth2Tradeshift;
use Appwrite\Platform\Modules\Project\Http\Project\OAuth2\TradeshiftSandbox\Update as UpdateOAuth2TradeshiftSandbox;
use Appwrite\Platform\Modules\Project\Http\Project\OAuth2\Twitch\Update as UpdateOAuth2Twitch;
use Appwrite\Platform\Modules\Project\Http\Project\OAuth2\WordPress\Update as UpdateOAuth2WordPress;
use Appwrite\Platform\Modules\Project\Http\Project\OAuth2\X\Update as UpdateOAuth2X;
use Appwrite\Platform\Modules\Project\Http\Project\OAuth2\XList as ListOAuth2Providers;
use Appwrite\Platform\Modules\Project\Http\Project\OAuth2\Yahoo\Update as UpdateOAuth2Yahoo;
use Appwrite\Platform\Modules\Project\Http\Project\OAuth2\Yandex\Update as UpdateOAuth2Yandex;
use Appwrite\Platform\Modules\Project\Http\Project\OAuth2\Zoho\Update as UpdateOAuth2Zoho;
use Appwrite\Platform\Modules\Project\Http\Project\OAuth2\Zoom\Update as UpdateOAuth2Zoom;
use Appwrite\Platform\Modules\Project\Http\Project\Platforms\Android\Create as CreateAndroidPlatform;
use Appwrite\Platform\Modules\Project\Http\Project\Platforms\Android\Update as UpdateAndroidPlatform;
use Appwrite\Platform\Modules\Project\Http\Project\Platforms\Apple\Create as CreateApplePlatform;
@@ -129,5 +173,51 @@ class Http extends Service
// Auth Methods
$this->addAction(UpdateAuthMethod::getName(), new UpdateAuthMethod());
// OAuth2
$this->addAction(ListOAuth2Providers::getName(), new ListOAuth2Providers());
$this->addAction(GetOAuth2Provider::getName(), new GetOAuth2Provider());
$this->addAction(UpdateOAuth2GitHub::getName(), new UpdateOAuth2GitHub());
$this->addAction(UpdateOAuth2Discord::getName(), new UpdateOAuth2Discord());
$this->addAction(UpdateOAuth2Figma::getName(), new UpdateOAuth2Figma());
$this->addAction(UpdateOAuth2Dropbox::getName(), new UpdateOAuth2Dropbox());
$this->addAction(UpdateOAuth2Dailymotion::getName(), new UpdateOAuth2Dailymotion());
$this->addAction(UpdateOAuth2Bitbucket::getName(), new UpdateOAuth2Bitbucket());
$this->addAction(UpdateOAuth2Bitly::getName(), new UpdateOAuth2Bitly());
$this->addAction(UpdateOAuth2Box::getName(), new UpdateOAuth2Box());
$this->addAction(UpdateOAuth2Autodesk::getName(), new UpdateOAuth2Autodesk());
$this->addAction(UpdateOAuth2Google::getName(), new UpdateOAuth2Google());
$this->addAction(UpdateOAuth2Zoom::getName(), new UpdateOAuth2Zoom());
$this->addAction(UpdateOAuth2Zoho::getName(), new UpdateOAuth2Zoho());
$this->addAction(UpdateOAuth2Yandex::getName(), new UpdateOAuth2Yandex());
$this->addAction(UpdateOAuth2X::getName(), new UpdateOAuth2X());
$this->addAction(UpdateOAuth2WordPress::getName(), new UpdateOAuth2WordPress());
$this->addAction(UpdateOAuth2Twitch::getName(), new UpdateOAuth2Twitch());
$this->addAction(UpdateOAuth2Stripe::getName(), new UpdateOAuth2Stripe());
$this->addAction(UpdateOAuth2Spotify::getName(), new UpdateOAuth2Spotify());
$this->addAction(UpdateOAuth2Slack::getName(), new UpdateOAuth2Slack());
$this->addAction(UpdateOAuth2Podio::getName(), new UpdateOAuth2Podio());
$this->addAction(UpdateOAuth2Notion::getName(), new UpdateOAuth2Notion());
$this->addAction(UpdateOAuth2Salesforce::getName(), new UpdateOAuth2Salesforce());
$this->addAction(UpdateOAuth2Yahoo::getName(), new UpdateOAuth2Yahoo());
$this->addAction(UpdateOAuth2Linkedin::getName(), new UpdateOAuth2Linkedin());
$this->addAction(UpdateOAuth2Disqus::getName(), new UpdateOAuth2Disqus());
$this->addAction(UpdateOAuth2Amazon::getName(), new UpdateOAuth2Amazon());
$this->addAction(UpdateOAuth2Etsy::getName(), new UpdateOAuth2Etsy());
$this->addAction(UpdateOAuth2Facebook::getName(), new UpdateOAuth2Facebook());
$this->addAction(UpdateOAuth2Tradeshift::getName(), new UpdateOAuth2Tradeshift());
$this->addAction(UpdateOAuth2TradeshiftSandbox::getName(), new UpdateOAuth2TradeshiftSandbox());
$this->addAction(UpdateOAuth2Paypal::getName(), new UpdateOAuth2Paypal());
$this->addAction(UpdateOAuth2PaypalSandbox::getName(), new UpdateOAuth2PaypalSandbox());
$this->addAction(UpdateOAuth2Gitlab::getName(), new UpdateOAuth2Gitlab());
$this->addAction(UpdateOAuth2Authentik::getName(), new UpdateOAuth2Authentik());
$this->addAction(UpdateOAuth2Auth0::getName(), new UpdateOAuth2Auth0());
$this->addAction(UpdateOAuth2FusionAuth::getName(), new UpdateOAuth2FusionAuth());
$this->addAction(UpdateOAuth2Keycloak::getName(), new UpdateOAuth2Keycloak());
$this->addAction(UpdateOAuth2Oidc::getName(), new UpdateOAuth2Oidc());
$this->addAction(UpdateOAuth2Okta::getName(), new UpdateOAuth2Okta());
$this->addAction(UpdateOAuth2Kick::getName(), new UpdateOAuth2Kick());
$this->addAction(UpdateOAuth2Apple::getName(), new UpdateOAuth2Apple());
$this->addAction(UpdateOAuth2Microsoft::getName(), new UpdateOAuth2Microsoft());
}
}
@@ -391,6 +391,8 @@ class Migrations extends Action
'keys.write',
'platforms.read',
'platforms.write',
'oauth2.read',
'oauth2.write',
'mocks.read',
'mocks.write',
'policies.read',
+44
View File
@@ -278,6 +278,47 @@ class Response extends SwooleResponse
public const MODEL_VCS = 'vcs';
public const MODEL_EMAIL_TEMPLATE = 'emailTemplate';
public const MODEL_EMAIL_TEMPLATE_LIST = 'emailTemplateList';
public const MODEL_OAUTH2_GITHUB = 'oAuth2Github';
public const MODEL_OAUTH2_DISCORD = 'oAuth2Discord';
public const MODEL_OAUTH2_FIGMA = 'oAuth2Figma';
public const MODEL_OAUTH2_DROPBOX = 'oAuth2Dropbox';
public const MODEL_OAUTH2_DAILYMOTION = 'oAuth2Dailymotion';
public const MODEL_OAUTH2_BITBUCKET = 'oAuth2Bitbucket';
public const MODEL_OAUTH2_BITLY = 'oAuth2Bitly';
public const MODEL_OAUTH2_BOX = 'oAuth2Box';
public const MODEL_OAUTH2_AUTODESK = 'oAuth2Autodesk';
public const MODEL_OAUTH2_GOOGLE = 'oAuth2Google';
public const MODEL_OAUTH2_ZOOM = 'oAuth2Zoom';
public const MODEL_OAUTH2_ZOHO = 'oAuth2Zoho';
public const MODEL_OAUTH2_YANDEX = 'oAuth2Yandex';
public const MODEL_OAUTH2_X = 'oAuth2X';
public const MODEL_OAUTH2_WORDPRESS = 'oAuth2WordPress';
public const MODEL_OAUTH2_TWITCH = 'oAuth2Twitch';
public const MODEL_OAUTH2_STRIPE = 'oAuth2Stripe';
public const MODEL_OAUTH2_SPOTIFY = 'oAuth2Spotify';
public const MODEL_OAUTH2_SLACK = 'oAuth2Slack';
public const MODEL_OAUTH2_PODIO = 'oAuth2Podio';
public const MODEL_OAUTH2_NOTION = 'oAuth2Notion';
public const MODEL_OAUTH2_SALESFORCE = 'oAuth2Salesforce';
public const MODEL_OAUTH2_YAHOO = 'oAuth2Yahoo';
public const MODEL_OAUTH2_LINKEDIN = 'oAuth2Linkedin';
public const MODEL_OAUTH2_DISQUS = 'oAuth2Disqus';
public const MODEL_OAUTH2_AMAZON = 'oAuth2Amazon';
public const MODEL_OAUTH2_ETSY = 'oAuth2Etsy';
public const MODEL_OAUTH2_FACEBOOK = 'oAuth2Facebook';
public const MODEL_OAUTH2_TRADESHIFT = 'oAuth2Tradeshift';
public const MODEL_OAUTH2_PAYPAL = 'oAuth2Paypal';
public const MODEL_OAUTH2_GITLAB = 'oAuth2Gitlab';
public const MODEL_OAUTH2_AUTHENTIK = 'oAuth2Authentik';
public const MODEL_OAUTH2_AUTH0 = 'oAuth2Auth0';
public const MODEL_OAUTH2_FUSIONAUTH = 'oAuth2FusionAuth';
public const MODEL_OAUTH2_KEYCLOAK = 'oAuth2Keycloak';
public const MODEL_OAUTH2_OIDC = 'oAuth2Oidc';
public const MODEL_OAUTH2_APPLE = 'oAuth2Apple';
public const MODEL_OAUTH2_OKTA = 'oAuth2Okta';
public const MODEL_OAUTH2_KICK = 'oAuth2Kick';
public const MODEL_OAUTH2_MICROSOFT = 'oAuth2Microsoft';
public const MODEL_OAUTH2_PROVIDER_LIST = 'oAuth2ProviderList';
// Health
public const MODEL_HEALTH_STATUS = 'healthStatus';
@@ -290,6 +331,9 @@ class Response extends SwooleResponse
// Console
public const MODEL_CONSOLE_VARIABLES = 'consoleVariables';
public const MODEL_CONSOLE_OAUTH2_PROVIDER_PARAMETER = 'consoleOAuth2ProviderParameter';
public const MODEL_CONSOLE_OAUTH2_PROVIDER = 'consoleOAuth2Provider';
public const MODEL_CONSOLE_OAUTH2_PROVIDER_LIST = 'consoleOAuth2ProviderList';
// Deprecated
public const MODEL_PERMISSIONS = 'permissions';
@@ -30,9 +30,9 @@ class AuthProvider extends Model
])
->addRule('secret', [
'type' => self::TYPE_STRING,
'description' => 'OAuth 2.0 application secret. Might be JSON string if provider requires extra configuration.',
'description' => 'OAuth 2.0 application secret. Might be JSON string if provider requires extra configuration. This property is write-only and always returned empty.',
'default' => '',
'example' => 'Bpw_g9c2TGXxfgLshDbSaL8tsCcqgczQ',
'example' => '',
])
->addRule('enabled', [
'type' => self::TYPE_BOOLEAN,
@@ -0,0 +1,37 @@
<?php
namespace Appwrite\Utopia\Response\Model;
use Appwrite\Utopia\Response;
use Appwrite\Utopia\Response\Model;
class ConsoleOAuth2Provider extends Model
{
public function __construct()
{
$this
->addRule('$id', [
'type' => self::TYPE_STRING,
'description' => 'OAuth2 provider ID.',
'default' => '',
'example' => 'github',
])
->addRule('parameters', [
'type' => Response::MODEL_CONSOLE_OAUTH2_PROVIDER_PARAMETER,
'description' => 'List of parameters required to configure this OAuth2 provider.',
'default' => [],
'array' => true,
])
;
}
public function getName(): string
{
return 'Console OAuth2 Provider';
}
public function getType(): string
{
return Response::MODEL_CONSOLE_OAUTH2_PROVIDER;
}
}
@@ -0,0 +1,37 @@
<?php
namespace Appwrite\Utopia\Response\Model;
use Appwrite\Utopia\Response;
use Appwrite\Utopia\Response\Model;
class ConsoleOAuth2ProviderList extends Model
{
public function __construct()
{
$this
->addRule('total', [
'type' => self::TYPE_INTEGER,
'description' => 'Total number of OAuth2 providers exposed by the server.',
'default' => 0,
'example' => 5,
])
->addRule('oAuth2Providers', [
'type' => Response::MODEL_CONSOLE_OAUTH2_PROVIDER,
'description' => 'List of OAuth2 providers, each with the parameters required to configure it.',
'default' => [],
'array' => true,
])
;
}
public function getName(): string
{
return 'Console OAuth2 Providers List';
}
public function getType(): string
{
return Response::MODEL_CONSOLE_OAUTH2_PROVIDER_LIST;
}
}
@@ -0,0 +1,49 @@
<?php
namespace Appwrite\Utopia\Response\Model;
use Appwrite\Utopia\Response;
use Appwrite\Utopia\Response\Model;
class ConsoleOAuth2ProviderParameter extends Model
{
public function __construct()
{
$this
->addRule('$id', [
'type' => self::TYPE_STRING,
'description' => 'Parameter ID. Maps to the request body field used by the project OAuth2 update endpoint (e.g. `clientId`, `appKey`, `tenant`).',
'default' => '',
'example' => 'clientId',
])
->addRule('name', [
'type' => self::TYPE_STRING,
'description' => 'Verbose, user-facing parameter name as shown in the provider\'s own dashboard. Includes alternate names when the provider exposes more than one.',
'default' => '',
'example' => 'Client ID or App ID',
])
->addRule('example', [
'type' => self::TYPE_STRING,
'description' => 'Example value for this parameter.',
'default' => '',
'example' => 'e4d87900000000540733',
])
->addRule('hint', [
'type' => self::TYPE_STRING,
'description' => 'Optional hint for this parameter, typically calling out a common wrong value. Empty string when no hint is set.',
'default' => '',
'example' => 'Example of wrong value: 370006',
])
;
}
public function getName(): string
{
return 'Console OAuth2 Provider Parameter';
}
public function getType(): string
{
return Response::MODEL_CONSOLE_OAUTH2_PROVIDER_PARAMETER;
}
}
@@ -0,0 +1,47 @@
<?php
namespace Appwrite\Utopia\Response\Model;
use Appwrite\Utopia\Response;
class OAuth2Amazon extends OAuth2Base
{
public array $conditions = [
'$id' => 'amazon',
];
public function getProviderLabel(): string
{
return 'Amazon';
}
public function getClientIdExample(): string
{
return 'amzn1.application-oa2-client.87400c00000000000000000000063d5b2';
}
public function getClientSecretExample(): string
{
return '79ffe4000000000000000000000000000000000000000000000000000002de55';
}
/**
* Get Name
*
* @return string
*/
public function getName(): string
{
return 'OAuth2Amazon';
}
/**
* Get Type
*
* @return string
*/
public function getType(): string
{
return Response::MODEL_OAUTH2_AMAZON;
}
}
@@ -0,0 +1,103 @@
<?php
namespace Appwrite\Utopia\Response\Model;
use Appwrite\Utopia\Response;
class OAuth2Apple extends OAuth2Base
{
public array $conditions = [
'$id' => 'apple',
];
public function getProviderLabel(): string
{
return 'Apple';
}
public function getClientIdExample(): string
{
return 'ip.appwrite.app.web';
}
public function getClientSecretExample(): string
{
// Unused: this model overrides __construct() to expose keyId, teamId
// and p8File instead of a single clientSecret field.
return '';
}
public function getClientIdFieldName(): string
{
return 'serviceId';
}
public function getClientIdLabel(): string
{
return 'service ID';
}
public function __construct()
{
// Apple's OAuth2 app credential is split into three fields (.p8 file
// contents, Key ID, Team ID) instead of a single clientSecret, so the
// rules are defined manually rather than delegating to OAuth2Base.
$this
->addRule('$id', [
'type' => self::TYPE_STRING,
'description' => 'OAuth2 provider ID.',
'default' => '',
'example' => 'apple',
])
->addRule('enabled', [
'type' => self::TYPE_BOOLEAN,
'description' => 'OAuth2 provider is active and can be used to create sessions.',
'default' => false,
'example' => false,
])
->addRule($this->getClientIdFieldName(), [
'type' => self::TYPE_STRING,
'description' => $this->getClientIdDescription(),
'default' => '',
'example' => $this->getClientIdExample(),
])
->addRule('keyId', [
'type' => self::TYPE_STRING,
'description' => 'Apple OAuth2 key ID.',
'default' => '',
'example' => 'P4000000N8',
])
->addRule('teamId', [
'type' => self::TYPE_STRING,
'description' => 'Apple OAuth2 team ID.',
'default' => '',
'example' => 'D4000000R6',
])
->addRule('p8File', [
'type' => self::TYPE_STRING,
'description' => 'Apple OAuth2 .p8 private key file contents. The secret key wrapped by the PEM markers is 200 characters long.',
'default' => '',
'example' => '-----BEGIN PRIVATE KEY-----MIGTAg...jy2Xbna-----END PRIVATE KEY-----',
]);
}
/**
* Get Name
*
* @return string
*/
public function getName(): string
{
return 'OAuth2Apple';
}
/**
* Get Type
*
* @return string
*/
public function getType(): string
{
return Response::MODEL_OAUTH2_APPLE;
}
}
@@ -0,0 +1,59 @@
<?php
namespace Appwrite\Utopia\Response\Model;
use Appwrite\Utopia\Response;
class OAuth2Auth0 extends OAuth2Base
{
public array $conditions = [
'$id' => 'auth0',
];
public function getProviderLabel(): string
{
return 'Auth0';
}
public function getClientIdExample(): string
{
return 'OaOkIA000000000000000000005KLSYq';
}
public function getClientSecretExample(): string
{
return 'zXz0000-00000000000000000000000000000-00000000000000000000PJafnF';
}
public function __construct()
{
parent::__construct();
$this->addRule('endpoint', [
'type' => self::TYPE_STRING,
'description' => 'Auth0 OAuth2 endpoint domain.',
'default' => '',
'example' => 'example.us.auth0.com',
]);
}
/**
* Get Name
*
* @return string
*/
public function getName(): string
{
return 'OAuth2Auth0';
}
/**
* Get Type
*
* @return string
*/
public function getType(): string
{
return Response::MODEL_OAUTH2_AUTH0;
}
}
@@ -0,0 +1,59 @@
<?php
namespace Appwrite\Utopia\Response\Model;
use Appwrite\Utopia\Response;
class OAuth2Authentik extends OAuth2Base
{
public array $conditions = [
'$id' => 'authentik',
];
public function getProviderLabel(): string
{
return 'Authentik';
}
public function getClientIdExample(): string
{
return 'dTKOPa0000000000000000000000000000e7G8hv';
}
public function getClientSecretExample(): string
{
return 'ntQadq000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000Hp5WK';
}
public function __construct()
{
parent::__construct();
$this->addRule('endpoint', [
'type' => self::TYPE_STRING,
'description' => 'Authentik OAuth2 endpoint domain.',
'default' => '',
'example' => 'example.authentik.com',
]);
}
/**
* Get Name
*
* @return string
*/
public function getName(): string
{
return 'OAuth2Authentik';
}
/**
* Get Type
*
* @return string
*/
public function getType(): string
{
return Response::MODEL_OAUTH2_AUTHENTIK;
}
}
@@ -0,0 +1,47 @@
<?php
namespace Appwrite\Utopia\Response\Model;
use Appwrite\Utopia\Response;
class OAuth2Autodesk extends OAuth2Base
{
public array $conditions = [
'$id' => 'autodesk',
];
public function getProviderLabel(): string
{
return 'Autodesk';
}
public function getClientIdExample(): string
{
return '5zw90v00000000000000000000kVYXN7';
}
public function getClientSecretExample(): string
{
return '7I000000000000MW';
}
/**
* Get Name
*
* @return string
*/
public function getName(): string
{
return 'OAuth2Autodesk';
}
/**
* Get Type
*
* @return string
*/
public function getType(): string
{
return Response::MODEL_OAUTH2_AUTODESK;
}
}
@@ -0,0 +1,125 @@
<?php
namespace Appwrite\Utopia\Response\Model;
use Appwrite\Utopia\Response\Model;
abstract class OAuth2Base extends Model
{
/**
* Provider display label used in rule descriptions.
*
* @return string e.g. 'GitHub', 'Discord', 'Dropbox'
*/
abstract public function getProviderLabel(): string;
/**
* Example value for the client ID rule.
*
* @return string
*/
abstract public function getClientIdExample(): string;
/**
* Example value for the client secret rule.
*
* @return string
*/
abstract public function getClientSecretExample(): string;
/**
* Public-facing field name of the client ID. Providers may override when
* they use different terminology (e.g. Dropbox -> 'appKey').
*
* @return string
*/
public function getClientIdFieldName(): string
{
return 'clientId';
}
/**
* Public-facing field name of the client secret. Providers may override
* when they use different terminology (e.g. Dropbox -> 'appSecret').
*
* @return string
*/
public function getClientSecretFieldName(): string
{
return 'clientSecret';
}
/**
* Human-readable label for the client ID, used in the generated rule
* description. Providers may override (e.g. Dropbox -> 'app key').
*
* @return string
*/
public function getClientIdLabel(): string
{
return 'client ID';
}
/**
* Human-readable label for the client secret, used in the generated rule
* description. Providers may override (e.g. Dropbox -> 'app secret').
*
* @return string
*/
public function getClientSecretLabel(): string
{
return 'client secret';
}
/**
* Rule description for the client ID. Auto-generated from the provider
* label and client ID label. Providers may override to add extra context.
*
* @return string
*/
public function getClientIdDescription(): string
{
return $this->getProviderLabel() . ' OAuth2 ' . $this->getClientIdLabel() . '.';
}
/**
* Rule description for the client secret. Auto-generated from the provider
* label and client secret label. Providers may override to add extra
* context.
*
* @return string
*/
public function getClientSecretDescription(): string
{
return $this->getProviderLabel() . ' OAuth2 ' . $this->getClientSecretLabel() . '.';
}
public function __construct()
{
$this
->addRule('$id', [
'type' => self::TYPE_STRING,
'description' => 'OAuth2 provider ID.',
'default' => '',
'example' => 'github',
])
->addRule('enabled', [
'type' => self::TYPE_BOOLEAN,
'description' => 'OAuth2 provider is active and can be used to create sessions.',
'default' => false,
'example' => false,
])
->addRule($this->getClientIdFieldName(), [
'type' => self::TYPE_STRING,
'description' => $this->getClientIdDescription(),
'default' => '',
'example' => $this->getClientIdExample(),
])
->addRule($this->getClientSecretFieldName(), [
'type' => self::TYPE_STRING,
'description' => $this->getClientSecretDescription(),
'default' => '',
'example' => $this->getClientSecretExample(),
]);
}
}
@@ -0,0 +1,67 @@
<?php
namespace Appwrite\Utopia\Response\Model;
use Appwrite\Utopia\Response;
class OAuth2Bitbucket extends OAuth2Base
{
public array $conditions = [
'$id' => 'bitbucket',
];
public function getProviderLabel(): string
{
return 'Bitbucket';
}
public function getClientIdExample(): string
{
return 'Knt70000000000ByRc';
}
public function getClientSecretExample(): string
{
return 'NMfLZJ00000000000000000000TLQdDx';
}
public function getClientIdFieldName(): string
{
return 'key';
}
public function getClientSecretFieldName(): string
{
return 'secret';
}
public function getClientIdLabel(): string
{
return 'key';
}
public function getClientSecretLabel(): string
{
return 'secret';
}
/**
* Get Name
*
* @return string
*/
public function getName(): string
{
return 'OAuth2Bitbucket';
}
/**
* Get Type
*
* @return string
*/
public function getType(): string
{
return Response::MODEL_OAUTH2_BITBUCKET;
}
}
@@ -0,0 +1,47 @@
<?php
namespace Appwrite\Utopia\Response\Model;
use Appwrite\Utopia\Response;
class OAuth2Bitly extends OAuth2Base
{
public array $conditions = [
'$id' => 'bitly',
];
public function getProviderLabel(): string
{
return 'Bitly';
}
public function getClientIdExample(): string
{
return 'd95151000000000000000000000000000067af9b';
}
public function getClientSecretExample(): string
{
return 'a13e250000000000000000000000000000d73095';
}
/**
* Get Name
*
* @return string
*/
public function getName(): string
{
return 'OAuth2Bitly';
}
/**
* Get Type
*
* @return string
*/
public function getType(): string
{
return Response::MODEL_OAUTH2_BITLY;
}
}
@@ -0,0 +1,47 @@
<?php
namespace Appwrite\Utopia\Response\Model;
use Appwrite\Utopia\Response;
class OAuth2Box extends OAuth2Base
{
public array $conditions = [
'$id' => 'box',
];
public function getProviderLabel(): string
{
return 'Box';
}
public function getClientIdExample(): string
{
return 'deglcs00000000000000000000x2og6y';
}
public function getClientSecretExample(): string
{
return 'OKM1f100000000000000000000eshEif';
}
/**
* Get Name
*
* @return string
*/
public function getName(): string
{
return 'OAuth2Box';
}
/**
* Get Type
*
* @return string
*/
public function getType(): string
{
return Response::MODEL_OAUTH2_BOX;
}
}
@@ -0,0 +1,67 @@
<?php
namespace Appwrite\Utopia\Response\Model;
use Appwrite\Utopia\Response;
class OAuth2Dailymotion extends OAuth2Base
{
public array $conditions = [
'$id' => 'dailymotion',
];
public function getProviderLabel(): string
{
return 'Dailymotion';
}
public function getClientIdExample(): string
{
return '07a9000000000000067f';
}
public function getClientSecretExample(): string
{
return 'a399a90000000000000000000000000000d90639';
}
public function getClientIdFieldName(): string
{
return 'apiKey';
}
public function getClientSecretFieldName(): string
{
return 'apiSecret';
}
public function getClientIdLabel(): string
{
return 'API key';
}
public function getClientSecretLabel(): string
{
return 'API secret';
}
/**
* Get Name
*
* @return string
*/
public function getName(): string
{
return 'OAuth2Dailymotion';
}
/**
* Get Type
*
* @return string
*/
public function getType(): string
{
return Response::MODEL_OAUTH2_DAILYMOTION;
}
}
@@ -0,0 +1,47 @@
<?php
namespace Appwrite\Utopia\Response\Model;
use Appwrite\Utopia\Response;
class OAuth2Discord extends OAuth2Base
{
public array $conditions = [
'$id' => 'discord',
];
public function getProviderLabel(): string
{
return 'Discord';
}
public function getClientIdExample(): string
{
return '950722000000343754';
}
public function getClientSecretExample(): string
{
return 'YmPXnM000000000000000000002zFg5D';
}
/**
* Get Name
*
* @return string
*/
public function getName(): string
{
return 'OAuth2Discord';
}
/**
* Get Type
*
* @return string
*/
public function getType(): string
{
return Response::MODEL_OAUTH2_DISCORD;
}
}
@@ -0,0 +1,67 @@
<?php
namespace Appwrite\Utopia\Response\Model;
use Appwrite\Utopia\Response;
class OAuth2Disqus extends OAuth2Base
{
public array $conditions = [
'$id' => 'disqus',
];
public function getProviderLabel(): string
{
return 'Disqus';
}
public function getClientIdExample(): string
{
return 'cgegH70000000000000000000000000000000000000000000000000000Hr1nYX';
}
public function getClientSecretExample(): string
{
return 'W7Bykj00000000000000000000000000000000000000000000000000003o43w9';
}
public function getClientIdFieldName(): string
{
return 'publicKey';
}
public function getClientSecretFieldName(): string
{
return 'secretKey';
}
public function getClientIdLabel(): string
{
return 'public key';
}
public function getClientSecretLabel(): string
{
return 'secret key';
}
/**
* Get Name
*
* @return string
*/
public function getName(): string
{
return 'OAuth2Disqus';
}
/**
* Get Type
*
* @return string
*/
public function getType(): string
{
return Response::MODEL_OAUTH2_DISQUS;
}
}
@@ -0,0 +1,67 @@
<?php
namespace Appwrite\Utopia\Response\Model;
use Appwrite\Utopia\Response;
class OAuth2Dropbox extends OAuth2Base
{
public array $conditions = [
'$id' => 'dropbox',
];
public function getProviderLabel(): string
{
return 'Dropbox';
}
public function getClientIdExample(): string
{
return 'jl000000000009t';
}
public function getClientSecretExample(): string
{
return 'g200000000000vw';
}
public function getClientIdFieldName(): string
{
return 'appKey';
}
public function getClientSecretFieldName(): string
{
return 'appSecret';
}
public function getClientIdLabel(): string
{
return 'app key';
}
public function getClientSecretLabel(): string
{
return 'app secret';
}
/**
* Get Name
*
* @return string
*/
public function getName(): string
{
return 'OAuth2Dropbox';
}
/**
* Get Type
*
* @return string
*/
public function getType(): string
{
return Response::MODEL_OAUTH2_DROPBOX;
}
}
@@ -0,0 +1,67 @@
<?php
namespace Appwrite\Utopia\Response\Model;
use Appwrite\Utopia\Response;
class OAuth2Etsy extends OAuth2Base
{
public array $conditions = [
'$id' => 'etsy',
];
public function getProviderLabel(): string
{
return 'Etsy';
}
public function getClientIdExample(): string
{
return 'nsgzxh0000000000008j85a2';
}
public function getClientSecretExample(): string
{
return 'tp000000ru';
}
public function getClientIdFieldName(): string
{
return 'keyString';
}
public function getClientSecretFieldName(): string
{
return 'sharedSecret';
}
public function getClientIdLabel(): string
{
return 'keystring';
}
public function getClientSecretLabel(): string
{
return 'shared secret';
}
/**
* Get Name
*
* @return string
*/
public function getName(): string
{
return 'OAuth2Etsy';
}
/**
* Get Type
*
* @return string
*/
public function getType(): string
{
return Response::MODEL_OAUTH2_ETSY;
}
}
@@ -0,0 +1,67 @@
<?php
namespace Appwrite\Utopia\Response\Model;
use Appwrite\Utopia\Response;
class OAuth2Facebook extends OAuth2Base
{
public array $conditions = [
'$id' => 'facebook',
];
public function getProviderLabel(): string
{
return 'Facebook';
}
public function getClientIdExample(): string
{
return '260600000007694';
}
public function getClientSecretExample(): string
{
return '2d0b2800000000000000000000d38af4';
}
public function getClientIdFieldName(): string
{
return 'appId';
}
public function getClientSecretFieldName(): string
{
return 'appSecret';
}
public function getClientIdLabel(): string
{
return 'app ID';
}
public function getClientSecretLabel(): string
{
return 'app secret';
}
/**
* Get Name
*
* @return string
*/
public function getName(): string
{
return 'OAuth2Facebook';
}
/**
* Get Type
*
* @return string
*/
public function getType(): string
{
return Response::MODEL_OAUTH2_FACEBOOK;
}
}
@@ -0,0 +1,47 @@
<?php
namespace Appwrite\Utopia\Response\Model;
use Appwrite\Utopia\Response;
class OAuth2Figma extends OAuth2Base
{
public array $conditions = [
'$id' => 'figma',
];
public function getProviderLabel(): string
{
return 'Figma';
}
public function getClientIdExample(): string
{
return 'byay5H0000000000VtiI40';
}
public function getClientSecretExample(): string
{
return 'yEpOYn0000000000000000004iIsU5';
}
/**
* Get Name
*
* @return string
*/
public function getName(): string
{
return 'OAuth2Figma';
}
/**
* Get Type
*
* @return string
*/
public function getType(): string
{
return Response::MODEL_OAUTH2_FIGMA;
}
}
@@ -0,0 +1,59 @@
<?php
namespace Appwrite\Utopia\Response\Model;
use Appwrite\Utopia\Response;
class OAuth2FusionAuth extends OAuth2Base
{
public array $conditions = [
'$id' => 'fusionauth',
];
public function getProviderLabel(): string
{
return 'FusionAuth';
}
public function getClientIdExample(): string
{
return 'b2222c00-0000-0000-0000-000000862097';
}
public function getClientSecretExample(): string
{
return 'Jx4s0C0000000000000000000000000000000wGqLsc';
}
public function __construct()
{
parent::__construct();
$this->addRule('endpoint', [
'type' => self::TYPE_STRING,
'description' => 'FusionAuth OAuth2 endpoint domain.',
'default' => '',
'example' => 'example.fusionauth.io',
]);
}
/**
* Get Name
*
* @return string
*/
public function getName(): string
{
return 'OAuth2FusionAuth';
}
/**
* Get Type
*
* @return string
*/
public function getType(): string
{
return Response::MODEL_OAUTH2_FUSIONAUTH;
}
}
@@ -0,0 +1,52 @@
<?php
namespace Appwrite\Utopia\Response\Model;
use Appwrite\Utopia\Response;
class OAuth2GitHub extends OAuth2Base
{
public array $conditions = [
'$id' => 'github',
];
public function getProviderLabel(): string
{
return 'GitHub';
}
public function getClientIdExample(): string
{
return 'e4d87900000000540733';
}
public function getClientSecretExample(): string
{
return '5e07c00000000000000000000000000000198bcc';
}
public function getClientIdDescription(): string
{
return parent::getClientIdDescription() . ' For GitHub Apps, use the "App ID" when both an App ID and client ID are available.';
}
/**
* Get Name
*
* @return string
*/
public function getName(): string
{
return 'OAuth2GitHub';
}
/**
* Get Type
*
* @return string
*/
public function getType(): string
{
return Response::MODEL_OAUTH2_GITHUB;
}
}
@@ -0,0 +1,79 @@
<?php
namespace Appwrite\Utopia\Response\Model;
use Appwrite\Utopia\Response;
class OAuth2Gitlab extends OAuth2Base
{
public array $conditions = [
'$id' => 'gitlab',
];
public function getProviderLabel(): string
{
return 'GitLab';
}
public function getClientIdExample(): string
{
return 'd41ffe0000000000000000000000000000000000000000000000000000d5e252';
}
public function getClientSecretExample(): string
{
return 'gloas-838cfa0000000000000000000000000000000000000000000000000000ecbb38';
}
public function getClientIdFieldName(): string
{
return 'applicationId';
}
public function getClientSecretFieldName(): string
{
return 'secret';
}
public function getClientIdLabel(): string
{
return 'application ID';
}
public function getClientSecretLabel(): string
{
return 'secret';
}
public function __construct()
{
parent::__construct();
$this->addRule('endpoint', [
'type' => self::TYPE_STRING,
'description' => 'GitLab OAuth2 endpoint URL. Defaults to https://gitlab.com for self-hosted instances.',
'default' => '',
'example' => 'https://gitlab.com',
]);
}
/**
* Get Name
*
* @return string
*/
public function getName(): string
{
return 'OAuth2Gitlab';
}
/**
* Get Type
*
* @return string
*/
public function getType(): string
{
return Response::MODEL_OAUTH2_GITLAB;
}
}
@@ -0,0 +1,47 @@
<?php
namespace Appwrite\Utopia\Response\Model;
use Appwrite\Utopia\Response;
class OAuth2Google extends OAuth2Base
{
public array $conditions = [
'$id' => 'google',
];
public function getProviderLabel(): string
{
return 'Google';
}
public function getClientIdExample(): string
{
return '120000000095-92ifjb00000000000000000000g7ijfb.apps.googleusercontent.com';
}
public function getClientSecretExample(): string
{
return 'GOCSPX-2k8gsR0000000000000000VNahJj';
}
/**
* Get Name
*
* @return string
*/
public function getName(): string
{
return 'OAuth2Google';
}
/**
* Get Type
*
* @return string
*/
public function getType(): string
{
return Response::MODEL_OAUTH2_GOOGLE;
}
}
@@ -0,0 +1,66 @@
<?php
namespace Appwrite\Utopia\Response\Model;
use Appwrite\Utopia\Response;
class OAuth2Keycloak extends OAuth2Base
{
public array $conditions = [
'$id' => 'keycloak',
];
public function getProviderLabel(): string
{
return 'Keycloak';
}
public function getClientIdExample(): string
{
return 'appwrite-o0000000st-app';
}
public function getClientSecretExample(): string
{
return 'jdjrJd00000000000000000000HUsaZO';
}
public function __construct()
{
parent::__construct();
$this->addRule('endpoint', [
'type' => self::TYPE_STRING,
'description' => 'Keycloak OAuth2 endpoint domain.',
'default' => '',
'example' => 'keycloak.example.com',
]);
$this->addRule('realmName', [
'type' => self::TYPE_STRING,
'description' => 'Keycloak OAuth2 realm name.',
'default' => '',
'example' => 'appwrite-realm',
]);
}
/**
* Get Name
*
* @return string
*/
public function getName(): string
{
return 'OAuth2Keycloak';
}
/**
* Get Type
*
* @return string
*/
public function getType(): string
{
return Response::MODEL_OAUTH2_KEYCLOAK;
}
}
@@ -0,0 +1,47 @@
<?php
namespace Appwrite\Utopia\Response\Model;
use Appwrite\Utopia\Response;
class OAuth2Kick extends OAuth2Base
{
public array $conditions = [
'$id' => 'kick',
];
public function getProviderLabel(): string
{
return 'Kick';
}
public function getClientIdExample(): string
{
return '01KQ7C00000000000001MFHS32';
}
public function getClientSecretExample(): string
{
return '34ac5600000000000000000000000000000000000000000000000000e830c8b';
}
/**
* Get Name
*
* @return string
*/
public function getName(): string
{
return 'OAuth2Kick';
}
/**
* Get Type
*
* @return string
*/
public function getType(): string
{
return Response::MODEL_OAUTH2_KICK;
}
}
@@ -0,0 +1,57 @@
<?php
namespace Appwrite\Utopia\Response\Model;
use Appwrite\Utopia\Response;
class OAuth2Linkedin extends OAuth2Base
{
public array $conditions = [
'$id' => 'linkedin',
];
public function getProviderLabel(): string
{
return 'LinkedIn';
}
public function getClientIdExample(): string
{
return '770000000000dv';
}
public function getClientSecretExample(): string
{
return 'WPL_AP1.2Bf0000000000000./HtlYw==';
}
public function getClientSecretFieldName(): string
{
return 'primaryClientSecret';
}
public function getClientSecretLabel(): string
{
return 'primary client secret';
}
/**
* Get Name
*
* @return string
*/
public function getName(): string
{
return 'OAuth2Linkedin';
}
/**
* Get Type
*
* @return string
*/
public function getType(): string
{
return Response::MODEL_OAUTH2_LINKEDIN;
}
}
@@ -0,0 +1,79 @@
<?php
namespace Appwrite\Utopia\Response\Model;
use Appwrite\Utopia\Response;
class OAuth2Microsoft extends OAuth2Base
{
public array $conditions = [
'$id' => 'microsoft',
];
public function getProviderLabel(): string
{
return 'Microsoft';
}
public function getClientIdExample(): string
{
return '00001111-aaaa-2222-bbbb-3333cccc4444';
}
public function getClientSecretExample(): string
{
return 'A1bC2dE3fH4iJ5kL6mN7oP8qR9sT0u';
}
public function getClientIdFieldName(): string
{
return 'applicationId';
}
public function getClientSecretFieldName(): string
{
return 'applicationSecret';
}
public function getClientIdLabel(): string
{
return 'application ID';
}
public function getClientSecretLabel(): string
{
return 'application secret';
}
public function __construct()
{
parent::__construct();
$this->addRule('tenant', [
'type' => self::TYPE_STRING,
'description' => 'Microsoft Entra ID tenant identifier. Use \'common\', \'organizations\', \'consumers\' or a specific tenant ID.',
'default' => '',
'example' => 'common',
]);
}
/**
* Get Name
*
* @return string
*/
public function getName(): string
{
return 'OAuth2Microsoft';
}
/**
* Get Type
*
* @return string
*/
public function getType(): string
{
return Response::MODEL_OAUTH2_MICROSOFT;
}
}
@@ -0,0 +1,57 @@
<?php
namespace Appwrite\Utopia\Response\Model;
use Appwrite\Utopia\Response;
class OAuth2Notion extends OAuth2Base
{
public array $conditions = [
'$id' => 'notion',
];
public function getProviderLabel(): string
{
return 'Notion';
}
public function getClientIdExample(): string
{
return '341d8700-0000-0000-0000-000000446ee3';
}
public function getClientSecretExample(): string
{
return 'secret_dLUr4b000000000000000000000000000000lFHAa9';
}
public function getClientIdFieldName(): string
{
return 'oauthClientId';
}
public function getClientSecretFieldName(): string
{
return 'oauthClientSecret';
}
/**
* Get Name
*
* @return string
*/
public function getName(): string
{
return 'OAuth2Notion';
}
/**
* Get Type
*
* @return string
*/
public function getType(): string
{
return Response::MODEL_OAUTH2_NOTION;
}
}
@@ -0,0 +1,78 @@
<?php
namespace Appwrite\Utopia\Response\Model;
use Appwrite\Utopia\Response;
class OAuth2Oidc extends OAuth2Base
{
public array $conditions = [
'$id' => 'oidc',
];
public function getProviderLabel(): string
{
return 'OpenID Connect';
}
public function getClientIdExample(): string
{
return 'qibI2x0000000000000000000000000006L2YFoG';
}
public function getClientSecretExample(): string
{
return 'Ah68ed000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003qpcHV';
}
public function __construct()
{
parent::__construct();
$this
->addRule('wellKnownURL', [
'type' => self::TYPE_STRING,
'description' => 'OpenID Connect well-known configuration URL. When set, authorization, token, and user info endpoints can be discovered automatically.',
'default' => '',
'example' => 'https://myoauth.com/.well-known/openid-configuration',
])
->addRule('authorizationURL', [
'type' => self::TYPE_STRING,
'description' => 'OpenID Connect authorization endpoint URL.',
'default' => '',
'example' => 'https://myoauth.com/oauth2/authorize',
])
->addRule('tokenUrl', [
'type' => self::TYPE_STRING,
'description' => 'OpenID Connect token endpoint URL.',
'default' => '',
'example' => 'https://myoauth.com/oauth2/token',
])
->addRule('userInfoUrl', [
'type' => self::TYPE_STRING,
'description' => 'OpenID Connect user info endpoint URL.',
'default' => '',
'example' => 'https://myoauth.com/oauth2/userinfo',
]);
}
/**
* Get Name
*
* @return string
*/
public function getName(): string
{
return 'OAuth2Oidc';
}
/**
* Get Type
*
* @return string
*/
public function getType(): string
{
return Response::MODEL_OAUTH2_OIDC;
}
}
@@ -0,0 +1,66 @@
<?php
namespace Appwrite\Utopia\Response\Model;
use Appwrite\Utopia\Response;
class OAuth2Okta extends OAuth2Base
{
public array $conditions = [
'$id' => 'okta',
];
public function getProviderLabel(): string
{
return 'Okta';
}
public function getClientIdExample(): string
{
return '0oa00000000000000698';
}
public function getClientSecretExample(): string
{
return 'Kiq0000000000000000000000000000000000000-00000000000H2L5-3SJ-vRV';
}
public function __construct()
{
parent::__construct();
$this->addRule('domain', [
'type' => self::TYPE_STRING,
'description' => 'Okta OAuth2 domain.',
'default' => '',
'example' => 'trial-6400025.okta.com',
]);
$this->addRule('authorizationServerId', [
'type' => self::TYPE_STRING,
'description' => 'Okta OAuth2 authorization server ID.',
'default' => '',
'example' => 'aus000000000000000h7z',
]);
}
/**
* Get Name
*
* @return string
*/
public function getName(): string
{
return 'OAuth2Okta';
}
/**
* Get Type
*
* @return string
*/
public function getType(): string
{
return Response::MODEL_OAUTH2_OKTA;
}
}
@@ -0,0 +1,57 @@
<?php
namespace Appwrite\Utopia\Response\Model;
use Appwrite\Utopia\Response;
class OAuth2Paypal extends OAuth2Base
{
public array $conditions = [
'$id' => ['paypal', 'paypalSandbox'],
];
public function getProviderLabel(): string
{
return 'PayPal';
}
public function getClientIdExample(): string
{
return 'AdhIEG7-000000000000-0000000000000000000000000000000-0000000000000000000000-2pyB';
}
public function getClientSecretExample(): string
{
return 'EH8KCXtew--000000000000000000000000000000000000000_C-1_5UP_000000000000000CB7KDp';
}
public function getClientSecretFieldName(): string
{
return 'secretKey';
}
public function getClientSecretLabel(): string
{
return 'secret key';
}
/**
* Get Name
*
* @return string
*/
public function getName(): string
{
return 'OAuth2Paypal';
}
/**
* Get Type
*
* @return string
*/
public function getType(): string
{
return Response::MODEL_OAUTH2_PAYPAL;
}
}
@@ -0,0 +1,47 @@
<?php
namespace Appwrite\Utopia\Response\Model;
use Appwrite\Utopia\Response;
class OAuth2Podio extends OAuth2Base
{
public array $conditions = [
'$id' => 'podio',
];
public function getProviderLabel(): string
{
return 'Podio';
}
public function getClientIdExample(): string
{
return 'appwrite-oauth-test-app';
}
public function getClientSecretExample(): string
{
return 'Rn247T0000000000000000000000000000000000000000000000000000W2zWTN';
}
/**
* Get Name
*
* @return string
*/
public function getName(): string
{
return 'OAuth2Podio';
}
/**
* Get Type
*
* @return string
*/
public function getType(): string
{
return Response::MODEL_OAUTH2_PODIO;
}
}
@@ -0,0 +1,78 @@
<?php
namespace Appwrite\Utopia\Response\Model;
use Appwrite\Utopia\Response;
use Appwrite\Utopia\Response\Model;
class OAuth2ProviderList extends Model
{
public function __construct()
{
$this
->addRule('total', [
'type' => self::TYPE_INTEGER,
'description' => 'Total number of OAuth2 providers in the given project.',
'default' => 0,
'example' => 5,
])
->addRule('providers', [
'type' => [
Response::MODEL_OAUTH2_GITHUB,
Response::MODEL_OAUTH2_DISCORD,
Response::MODEL_OAUTH2_FIGMA,
Response::MODEL_OAUTH2_DROPBOX,
Response::MODEL_OAUTH2_DAILYMOTION,
Response::MODEL_OAUTH2_BITBUCKET,
Response::MODEL_OAUTH2_BITLY,
Response::MODEL_OAUTH2_BOX,
Response::MODEL_OAUTH2_AUTODESK,
Response::MODEL_OAUTH2_GOOGLE,
Response::MODEL_OAUTH2_ZOOM,
Response::MODEL_OAUTH2_ZOHO,
Response::MODEL_OAUTH2_YANDEX,
Response::MODEL_OAUTH2_X,
Response::MODEL_OAUTH2_WORDPRESS,
Response::MODEL_OAUTH2_TWITCH,
Response::MODEL_OAUTH2_STRIPE,
Response::MODEL_OAUTH2_SPOTIFY,
Response::MODEL_OAUTH2_SLACK,
Response::MODEL_OAUTH2_PODIO,
Response::MODEL_OAUTH2_NOTION,
Response::MODEL_OAUTH2_SALESFORCE,
Response::MODEL_OAUTH2_YAHOO,
Response::MODEL_OAUTH2_LINKEDIN,
Response::MODEL_OAUTH2_DISQUS,
Response::MODEL_OAUTH2_AMAZON,
Response::MODEL_OAUTH2_ETSY,
Response::MODEL_OAUTH2_FACEBOOK,
Response::MODEL_OAUTH2_TRADESHIFT,
Response::MODEL_OAUTH2_PAYPAL,
Response::MODEL_OAUTH2_GITLAB,
Response::MODEL_OAUTH2_AUTHENTIK,
Response::MODEL_OAUTH2_AUTH0,
Response::MODEL_OAUTH2_FUSIONAUTH,
Response::MODEL_OAUTH2_KEYCLOAK,
Response::MODEL_OAUTH2_OIDC,
Response::MODEL_OAUTH2_APPLE,
Response::MODEL_OAUTH2_OKTA,
Response::MODEL_OAUTH2_KICK,
Response::MODEL_OAUTH2_MICROSOFT,
],
'description' => 'List of OAuth2 providers.',
'default' => [],
'array' => true,
])
;
}
public function getName(): string
{
return 'OAuth2 Providers List';
}
public function getType(): string
{
return Response::MODEL_OAUTH2_PROVIDER_LIST;
}
}

Some files were not shown because too many files have changed in this diff Show More