mirror of
https://github.com/appwrite/appwrite.git
synced 2026-05-26 13:51:13 +00:00
Merge branch '1.8.x' into feat-tanstack-start-sites
This commit is contained in:
+639
-251
@@ -1,266 +1,654 @@
|
||||
# Version 1.8.0
|
||||
|
||||
## What's Changed
|
||||
|
||||
### Notable changes
|
||||
|
||||
* Do not allow full range in [#9847](https://github.com/appwrite/appwrite/pull/9847)
|
||||
* Expose internal id as a part of auto increment id in [#9713](https://github.com/appwrite/appwrite/pull/9713)
|
||||
* Expose sequence in [#9870](https://github.com/appwrite/appwrite/pull/9870)
|
||||
* Add flutter 3.32 and dart 3.8 runtimes in [#9914](https://github.com/appwrite/appwrite/pull/9914)
|
||||
* Shorten commit url and branch url in [#9919](https://github.com/appwrite/appwrite/pull/9919)
|
||||
* Remove powered by from error pages in [#9927](https://github.com/appwrite/appwrite/pull/9927)
|
||||
* Enable resource limits on GIF previews in [#9940](https://github.com/appwrite/appwrite/pull/9940)
|
||||
* Only run maintenance task for projects accessed in last 24 hours in [#9989](https://github.com/appwrite/appwrite/pull/9989)
|
||||
* Add increment + decrement routes in [#9986](https://github.com/appwrite/appwrite/pull/9986)
|
||||
* Only run maintenance task for projects accessed in last 30 days in [#9995](https://github.com/appwrite/appwrite/pull/9995)
|
||||
* Update appwrite-assistant image version to 0.8.3 in [#10003](https://github.com/appwrite/appwrite/pull/10003)
|
||||
* Update emails to use button in [#9590](https://github.com/appwrite/appwrite/pull/9590)
|
||||
* Create commit & branch url for first git deployment when site is linked to repo in [#9969](https://github.com/appwrite/appwrite/pull/9969)
|
||||
* Handle React Native schemes in [#9650](https://github.com/appwrite/appwrite/pull/9650)
|
||||
* Handle origin validation for web extensions in [#10107](https://github.com/appwrite/appwrite/pull/10107)
|
||||
* Preview text for emails in [#10198](https://github.com/appwrite/appwrite/pull/10198)
|
||||
* Create email target when using email OTP registration in [#10224](https://github.com/appwrite/appwrite/pull/10224)
|
||||
* Add CSV imports in [#10231](https://github.com/appwrite/appwrite/pull/10231)
|
||||
* Add support for svg favicons in [#10255](https://github.com/appwrite/appwrite/pull/10255)
|
||||
* Realtime support for bulk api in [#10096](https://github.com/appwrite/appwrite/pull/10096)
|
||||
* Skip redundant subqueries in users list route in [#10297](https://github.com/appwrite/appwrite/pull/10297)
|
||||
* Add native sign in with Apple function template in [#10286](https://github.com/appwrite/appwrite/pull/10286)
|
||||
* Add support for HEAD requests in [#10304](https://github.com/appwrite/appwrite/pull/10304)
|
||||
* Update invite email copy in [#10309](https://github.com/appwrite/appwrite/pull/10309)
|
||||
* Increase dynamic API key expiration in [#10328](https://github.com/appwrite/appwrite/pull/10328)
|
||||
* Add TablesDB service in [#10333](https://github.com/appwrite/appwrite/pull/10333)
|
||||
* Add execution.deploymentId to response model in [#10357](https://github.com/appwrite/appwrite/pull/10357)
|
||||
* Switch Union China Pay to just Union Pay in [#10372](https://github.com/appwrite/appwrite/pull/10372) and [#10382](https://github.com/appwrite/appwrite/pull/10382)
|
||||
* Add execution id and log id to response headers in [#10379](https://github.com/appwrite/appwrite/pull/10379)
|
||||
* Add executionId and client IP to function headers in [#9147](https://github.com/appwrite/appwrite/pull/9147)
|
||||
* Allow HEAD requests in function executions in [#10385](https://github.com/appwrite/appwrite/pull/10385)
|
||||
* Add support for select queries when listing deployments in [#10380](https://github.com/appwrite/appwrite/pull/10380)
|
||||
* Add spatial type attributes in [#10356](https://github.com/appwrite/appwrite/pull/10356) and [#10443](https://github.com/appwrite/appwrite/pull/10443)
|
||||
* Add realtime support for bulk upserts in [#10425](https://github.com/appwrite/appwrite/pull/10425)
|
||||
* Add previewUrl to vcs comment from vcs controller in [#10396](https://github.com/appwrite/appwrite/pull/10396)
|
||||
* Rename verification SDK methods to be more specific in [#10606](https://github.com/appwrite/appwrite/pull/10606)
|
||||
* Add project name in email subject in [#10609](https://github.com/appwrite/appwrite/pull/10609)
|
||||
* Throw error when email is not available for account verification in [#10533](https://github.com/appwrite/appwrite/pull/10533)
|
||||
* Add support for transactions in [#10023](https://github.com/appwrite/appwrite/pull/10023) and [#10624](https://github.com/appwrite/appwrite/pull/10624)
|
||||
* Use bcc only emails for smtp in [#10644](https://github.com/appwrite/appwrite/pull/10644)
|
||||
|
||||
### Fixes
|
||||
|
||||
* Fix rules on active deployment in [#9902](https://github.com/appwrite/appwrite/pull/9902)
|
||||
* Fix for upserts with differing optional parameter sets in [#9928](https://github.com/appwrite/appwrite/pull/9928)
|
||||
* Fix teams deletion in [#9888](https://github.com/appwrite/appwrite/pull/9888)
|
||||
* Fix deletion logic in [#9938](https://github.com/appwrite/appwrite/pull/9938)
|
||||
* Update database for upsert fix in [#9941](https://github.com/appwrite/appwrite/pull/9941)
|
||||
* Fix expire format in account recovery, verification, phone and mfa in [#9600](https://github.com/appwrite/appwrite/pull/9600)
|
||||
* Fix github comments and deployment creation on branch deletion in [#9949](https://github.com/appwrite/appwrite/pull/9949)
|
||||
* Fix cache issues with proxy for deployment download in [#9971](https://github.com/appwrite/appwrite/pull/9971)
|
||||
* Redirect rule parent resource in [#9982](https://github.com/appwrite/appwrite/pull/9982)
|
||||
* Fix usage queues in [#9946](https://github.com/appwrite/appwrite/pull/9946)
|
||||
* Transfer control for the migration in [#9997](https://github.com/appwrite/appwrite/pull/9997)
|
||||
* Prevent 'Attribute "factors" must be an array' error in [#10004](https://github.com/appwrite/appwrite/pull/10004)
|
||||
* Fix all vcs urls missing region in [#9998](https://github.com/appwrite/appwrite/pull/9998)
|
||||
* Add readable error for csv imports in [#9947](https://github.com/appwrite/appwrite/pull/9947)
|
||||
* Fix missing screenshot logs in [#10024](https://github.com/appwrite/appwrite/pull/10024)
|
||||
* Update executor to fix s3 endpoint bug in [#10036](https://github.com/appwrite/appwrite/pull/10036)
|
||||
* Fix build duration calculation in [#10053](https://github.com/appwrite/appwrite/pull/10053)
|
||||
* Fix logs order in [#10052](https://github.com/appwrite/appwrite/pull/10052)
|
||||
* Fix platform check for Sites with automatic rule in [#10043](https://github.com/appwrite/appwrite/pull/10043)
|
||||
* Increase cache ttl to ensure hits in [#10079](https://github.com/appwrite/appwrite/pull/10079)
|
||||
* Fix connect to existing repo flow in [#10034](https://github.com/appwrite/appwrite/pull/10034)
|
||||
* Fix migrations path and type in [#10090](https://github.com/appwrite/appwrite/pull/10090)
|
||||
* Fix JWT authentication database selection for admin mode in [#10098](https://github.com/appwrite/appwrite/pull/10098)
|
||||
* Use _APP_CONSOLE_DOMAIN, if not found, then use _APP_DOMAIN in [#9999](https://github.com/appwrite/appwrite/pull/9999)
|
||||
* Fix file tokens not working on file-security in [#10120](https://github.com/appwrite/appwrite/pull/10120)
|
||||
* Fix build activation race condition in [#9952](https://github.com/appwrite/appwrite/pull/9952)
|
||||
* Changed the default permission param of upsert document in [#10129](https://github.com/appwrite/appwrite/pull/10129)
|
||||
* Fix success validation in oauth2 redirect in [#10130](https://github.com/appwrite/appwrite/pull/10130)
|
||||
* Update OAuth2 redirect URLs in [#10119](https://github.com/appwrite/appwrite/pull/10119)
|
||||
* Fix specs with new env vars in [#10135](https://github.com/appwrite/appwrite/pull/10135)
|
||||
* Skip deployment when commit is created by us in [#10187](https://github.com/appwrite/appwrite/pull/10187)
|
||||
* Use direct source for file-preview when empty in [#10181](https://github.com/appwrite/appwrite/pull/10181)
|
||||
* Better error message for invalid function scheduled time in [#10201](https://github.com/appwrite/appwrite/pull/10201)
|
||||
* Add defaultBranch in getRepository response in [#10190](https://github.com/appwrite/appwrite/pull/10190)
|
||||
* Filter sequence to int because any models skip rule checks in [#10221](https://github.com/appwrite/appwrite/pull/10221)
|
||||
* Fix 500 errors on robots and humans txt files in [#10248](https://github.com/appwrite/appwrite/pull/10248)
|
||||
* Fix atomic number ops with limit 0 in [#10264](https://github.com/appwrite/appwrite/pull/10264)
|
||||
* Update build command for flutter in [#10288](https://github.com/appwrite/appwrite/pull/10288)
|
||||
* Add a fallback locale in [#10307](https://github.com/appwrite/appwrite/pull/10307)
|
||||
* Fix variables sharing across resources in [#10308](https://github.com/appwrite/appwrite/pull/10308)
|
||||
* Fix uncaught invalid arg in [#10318](https://github.com/appwrite/appwrite/pull/10318)
|
||||
* Add missing upsert event in [#10317](https://github.com/appwrite/appwrite/pull/10317)
|
||||
* Improve font reliability in [#10332](https://github.com/appwrite/appwrite/pull/10332)
|
||||
* Truncate logs in function worker in [#9773](https://github.com/appwrite/appwrite/pull/9773)
|
||||
* Fix event template configuration issues in [#10350](https://github.com/appwrite/appwrite/pull/10350)
|
||||
* Fix users events & missed publisher logic for Functions in [#10348](https://github.com/appwrite/appwrite/pull/10348)
|
||||
* Fix incorrect file token expiry in [#10329](https://github.com/appwrite/appwrite/pull/10329)
|
||||
* Fix upserting that makes no change in [#10363](https://github.com/appwrite/appwrite/pull/10363) and [#10364](https://github.com/appwrite/appwrite/pull/10364)
|
||||
* Fix domain validator in [#10374](https://github.com/appwrite/appwrite/pull/10374)
|
||||
* Apply sequence integer casting and attribute cleanup fixes to Row model, TablesDB tests, and document processing in [#10383](https://github.com/appwrite/appwrite/pull/10383)
|
||||
* Fix domain validator in [#10386](https://github.com/appwrite/appwrite/pull/10386)
|
||||
* Fix sequence removal in [#10388](https://github.com/appwrite/appwrite/pull/10388)
|
||||
* Fix TablesDB scopes in [#10387](https://github.com/appwrite/appwrite/pull/10387)
|
||||
* Fix request filter in [#10389](https://github.com/appwrite/appwrite/pull/10389)
|
||||
* Fix nested filter selects in [#10393](https://github.com/appwrite/appwrite/pull/10393)
|
||||
* Fix readonly attr stripping on write in [#10405](https://github.com/appwrite/appwrite/pull/10405)
|
||||
* Replace %s with mustache placeholder in [#10392](https://github.com/appwrite/appwrite/pull/10392)
|
||||
* Support array headers for set-cookie in [#10427](https://github.com/appwrite/appwrite/pull/10427)
|
||||
* Fix put prefs structure validation in [#10436](https://github.com/appwrite/appwrite/pull/10436)
|
||||
* Fix oauth identity check in [#10460](https://github.com/appwrite/appwrite/pull/10460)
|
||||
* Fix check in [#10489](https://github.com/appwrite/appwrite/pull/10489)
|
||||
* Fix database usage metrics in [#10483](https://github.com/appwrite/appwrite/pull/10483)
|
||||
* Throw appropriate 400s from request filters in [#10502](https://github.com/appwrite/appwrite/pull/10502)
|
||||
* Catch query exception on bucket/file list in [#10505](https://github.com/appwrite/appwrite/pull/10505)
|
||||
* Use outputDirectory attribute from deployment in [#10571](https://github.com/appwrite/appwrite/pull/10571)
|
||||
* Fix buildOutput attribute name in deployment check in [#10572](https://github.com/appwrite/appwrite/pull/10572)
|
||||
* Update database for nested selection fix in [#10577](https://github.com/appwrite/appwrite/pull/10577)
|
||||
* Auto-allow sites domain for OAuth in [#10503](https://github.com/appwrite/appwrite/pull/10503)
|
||||
* Handle OIDC well-known endpoint errors in [#10589](https://github.com/appwrite/appwrite/pull/10589)
|
||||
* Correct invalid template links in Create temporary deployment endpoint in [#10581](https://github.com/appwrite/appwrite/pull/10581)
|
||||
* Update broken create table links in TablesDB docs in [#10592](https://github.com/appwrite/appwrite/pull/10592)
|
||||
* Fix cross API compatibility in [#10626](https://github.com/appwrite/appwrite/pull/10626)
|
||||
* Fix code 0 from databases on realtime in [#10631](https://github.com/appwrite/appwrite/pull/10631)
|
||||
* Throw duplicate error when function id already exists in [#10618](https://github.com/appwrite/appwrite/pull/10618)
|
||||
|
||||
### Miscellaneous
|
||||
|
||||
* Fix task coroutine hooks in [#9850](https://github.com/appwrite/appwrite/pull/9850)
|
||||
* Feat sync encrypt updates in [#9871](https://github.com/appwrite/appwrite/pull/9871)
|
||||
* Revert "Feat sync encrypt updates" in [#9877](https://github.com/appwrite/appwrite/pull/9877)
|
||||
* Add builds worker group in [#9872](https://github.com/appwrite/appwrite/pull/9872)
|
||||
* Revert encrypted attribute changes in [#9898](https://github.com/appwrite/appwrite/pull/9898)
|
||||
* Update sdk generator and sdks in [#9849](https://github.com/appwrite/appwrite/pull/9849)
|
||||
* Release cli in [#9900](https://github.com/appwrite/appwrite/pull/9900)
|
||||
* Improve how rules are fetched in [#9915](https://github.com/appwrite/appwrite/pull/9915)
|
||||
* Sync 1.6 in [#9920](https://github.com/appwrite/appwrite/pull/9920)
|
||||
* Update messaging library in [#9764](https://github.com/appwrite/appwrite/pull/9764)
|
||||
* Disable TCP hook on stats resources in [#9932](https://github.com/appwrite/appwrite/pull/9932)
|
||||
* Remove JSON index on roles due to MySQL bug in [#9924](https://github.com/appwrite/appwrite/pull/9924)
|
||||
* Update queue in [#9936](https://github.com/appwrite/appwrite/pull/9936)
|
||||
* Fix flaky account tests in [#9954](https://github.com/appwrite/appwrite/pull/9954)
|
||||
* Fix flaky messaging test in [#9957](https://github.com/appwrite/appwrite/pull/9957)
|
||||
* Make usage tests robust in [#9956](https://github.com/appwrite/appwrite/pull/9956)
|
||||
* Increase deployment timeouts in tests in [#9955](https://github.com/appwrite/appwrite/pull/9955)
|
||||
* Graceful shutdown on SIGTERM in [#9890](https://github.com/appwrite/appwrite/pull/9890)
|
||||
* Bring back telemetry for storage in [#9903](https://github.com/appwrite/appwrite/pull/9903)
|
||||
* Update version to 1.7.4 and add experimental warnings in [#9959](https://github.com/appwrite/appwrite/pull/9959)
|
||||
* Return queue pre-fetch results in [#9731](https://github.com/appwrite/appwrite/pull/9731)
|
||||
* Update SDK versions in [#9987](https://github.com/appwrite/appwrite/pull/9987)
|
||||
* Restore unique filename for health check #9842 in [#9993](https://github.com/appwrite/appwrite/pull/9993)
|
||||
* Add after build hook in [#9996](https://github.com/appwrite/appwrite/pull/9996)
|
||||
* Remove endpoint selector in [#10000](https://github.com/appwrite/appwrite/pull/10000)
|
||||
* Use static code instead of astro in tests in [#9966](https://github.com/appwrite/appwrite/pull/9966)
|
||||
* Add ref param to vcs list contents in [#9991](https://github.com/appwrite/appwrite/pull/9991)
|
||||
* Update coderabbit config file in [#10005](https://github.com/appwrite/appwrite/pull/10005)
|
||||
* TAR support in [#10016](https://github.com/appwrite/appwrite/pull/10016)
|
||||
* Update delete project scope in [#10017](https://github.com/appwrite/appwrite/pull/10017)
|
||||
* Lazy-load relationships in [#9669](https://github.com/appwrite/appwrite/pull/9669)
|
||||
* Revert "Feat: Lazy-load relationships" in [#10018](https://github.com/appwrite/appwrite/pull/10018)
|
||||
* Revert "Update delete project scope" in [#10022](https://github.com/appwrite/appwrite/pull/10022)
|
||||
* 1.8.x in [#9985](https://github.com/appwrite/appwrite/pull/9985)
|
||||
* Update cli version and add bulk operation warnings in [#10007](https://github.com/appwrite/appwrite/pull/10007)
|
||||
* Update Appwrite description to include Sites, add MCP to products list in [#9867](https://github.com/appwrite/appwrite/pull/9867)
|
||||
* Update README.md in [#10026](https://github.com/appwrite/appwrite/pull/10026)
|
||||
* Fix duplication of platforms in swagger specs in [#10008](https://github.com/appwrite/appwrite/pull/10008)
|
||||
* Update react native sdk and changelog in [#10025](https://github.com/appwrite/appwrite/pull/10025)
|
||||
* Update delete project signature in [#10028](https://github.com/appwrite/appwrite/pull/10028)
|
||||
* Fix Golang SDK examples for docs in [#10001](https://github.com/appwrite/appwrite/pull/10001)
|
||||
* Revert "worker: Graceful shutdown on SIGTERM" in [#10035](https://github.com/appwrite/appwrite/pull/10035)
|
||||
* Fix benchmark CI in [#10055](https://github.com/appwrite/appwrite/pull/10055)
|
||||
* Use ->action(...)) instead of ->callback([$this, 'action']); in [#9967](https://github.com/appwrite/appwrite/pull/9967)
|
||||
* Override project via custom domains log in [#10011](https://github.com/appwrite/appwrite/pull/10011)
|
||||
* Add database worker job logging in [#10056](https://github.com/appwrite/appwrite/pull/10056)
|
||||
* Add runtimeEntrypoint param in [#10062](https://github.com/appwrite/appwrite/pull/10062)
|
||||
* Add missing injections in [#10061](https://github.com/appwrite/appwrite/pull/10061)
|
||||
* Replace Console loop with Swoole Timer for stats resource m… in [#10054](https://github.com/appwrite/appwrite/pull/10054)
|
||||
* Update README.md in [#10063](https://github.com/appwrite/appwrite/pull/10063)
|
||||
* Fix parameter order in action function for robots.txt route in [#10067](https://github.com/appwrite/appwrite/pull/10067)
|
||||
* Preview endpoint logging in [#10068](https://github.com/appwrite/appwrite/pull/10068)
|
||||
* Fix flakyness of account tests in [#10066](https://github.com/appwrite/appwrite/pull/10066)
|
||||
* Update cli to 8.1.0 and add changelog in [#10070](https://github.com/appwrite/appwrite/pull/10070)
|
||||
* Update composer.json and composer.lock to include appwrite-lab… in [#10051](https://github.com/appwrite/appwrite/pull/10051)
|
||||
* Fix tests, for `Cloud` in [#10085](https://github.com/appwrite/appwrite/pull/10085)
|
||||
* Update README.md in [#10084](https://github.com/appwrite/appwrite/pull/10084)
|
||||
* Revert "chore: update composer.json and composer.lock to include appwrite-lab…" in [#10086](https://github.com/appwrite/appwrite/pull/10086)
|
||||
* Update README to add Bulk API link in [#10095](https://github.com/appwrite/appwrite/pull/10095)
|
||||
* Add redis publisher to schedule base if available in [#10099](https://github.com/appwrite/appwrite/pull/10099)
|
||||
* Fix site template test in [#10104](https://github.com/appwrite/appwrite/pull/10104)
|
||||
* Update nodejs 17.1.0 in [#10088](https://github.com/appwrite/appwrite/pull/10088)
|
||||
* Update README.md to add Upsert announcement in [#10112](https://github.com/appwrite/appwrite/pull/10112)
|
||||
* Reduce delete batch size in [#10128](https://github.com/appwrite/appwrite/pull/10128)
|
||||
* Update README.md in [#10134](https://github.com/appwrite/appwrite/pull/10134)
|
||||
* Speed up tests in [#10127](https://github.com/appwrite/appwrite/pull/10127)
|
||||
* Update cli to 8.2.0 in [#10136](https://github.com/appwrite/appwrite/pull/10136)
|
||||
* Prevent injected $user from being shadowed in [#10150](https://github.com/appwrite/appwrite/pull/10150)
|
||||
* Update react native to 0.10.1 and dotnet to 0.14.0 in [#10138](https://github.com/appwrite/appwrite/pull/10138)
|
||||
* Update README.md in [#10153](https://github.com/appwrite/appwrite/pull/10153)
|
||||
* Update cli 8.2.1 in [#10155](https://github.com/appwrite/appwrite/pull/10155)
|
||||
* Fix build usage specification in [#10157](https://github.com/appwrite/appwrite/pull/10157)
|
||||
* Handle redirect validator in specs + GraphQL type mapper in [#10158](https://github.com/appwrite/appwrite/pull/10158)
|
||||
* Update dart 16.1.0, flutter 17.0.2 and cli 8.2.2 in [#10161](https://github.com/appwrite/appwrite/pull/10161)
|
||||
* Improve invalid scheme error in origin check in [#10164](https://github.com/appwrite/appwrite/pull/10164)
|
||||
* 1.7.x in [#9897](https://github.com/appwrite/appwrite/pull/9897)
|
||||
* Added the cases of null permissions in the upsert route and update th… in [#10179](https://github.com/appwrite/appwrite/pull/10179)
|
||||
* Fix 1.7.x specs in [#10197](https://github.com/appwrite/appwrite/pull/10197)
|
||||
* Suppress git-action exception in deployment worker in [#10199](https://github.com/appwrite/appwrite/pull/10199)
|
||||
* Stats-usage on redis in [#10156](https://github.com/appwrite/appwrite/pull/10156)
|
||||
* Fix templates on `1.7.x`. in [#10203](https://github.com/appwrite/appwrite/pull/10203)
|
||||
* Change preview & body for MFA email in [#10205](https://github.com/appwrite/appwrite/pull/10205)
|
||||
* Add docs for nestedType, encode, from and toMap in [#10204](https://github.com/appwrite/appwrite/pull/10204)
|
||||
* Update sdks 1.7.x in [#10202](https://github.com/appwrite/appwrite/pull/10202)
|
||||
* Update migration release in [#10222](https://github.com/appwrite/appwrite/pull/10222)
|
||||
* Remove sequence on incoming docs in [#10228](https://github.com/appwrite/appwrite/pull/10228)
|
||||
* Filter certificates renewal task in maintenance by region in [#10227](https://github.com/appwrite/appwrite/pull/10227)
|
||||
* Move changelog to sdks platforms array in [#10233](https://github.com/appwrite/appwrite/pull/10233)
|
||||
* Update changelog and sdk gen in [#10247](https://github.com/appwrite/appwrite/pull/10247)
|
||||
* Telemetry for cache hits and misses in [#10240](https://github.com/appwrite/appwrite/pull/10240)
|
||||
* Add model examples + additonal examples to specs in [#10249](https://github.com/appwrite/appwrite/pull/10249)
|
||||
* Update favicons endpoint to fallback to ico instead of throwing error in [#10260](https://github.com/appwrite/appwrite/pull/10260)
|
||||
* Update README.md in [#10259](https://github.com/appwrite/appwrite/pull/10259)
|
||||
* Check CAA record before issuing certificate in [#10258](https://github.com/appwrite/appwrite/pull/10258)
|
||||
* Revert "Check CAA record before issuing certificate" in [#10263](https://github.com/appwrite/appwrite/pull/10263)
|
||||
* Test var id attribute in [#10243](https://github.com/appwrite/appwrite/pull/10243)
|
||||
* Add type attribute to the database creation flow in [#10266](https://github.com/appwrite/appwrite/pull/10266)
|
||||
* Add CAA validator in [#10267](https://github.com/appwrite/appwrite/pull/10267)
|
||||
* Update database type to grids and legacy in [#10273](https://github.com/appwrite/appwrite/pull/10273)
|
||||
* Update README.md in [#10272](https://github.com/appwrite/appwrite/pull/10272)
|
||||
* Upgrade composer for utopia migration in [#10274](https://github.com/appwrite/appwrite/pull/10274)
|
||||
* Update SDK generator and sdks in [#10271](https://github.com/appwrite/appwrite/pull/10271)
|
||||
* Fix wrong resource path for audits in [#10279](https://github.com/appwrite/appwrite/pull/10279)
|
||||
* Update `grid` on resource events in [#10282](https://github.com/appwrite/appwrite/pull/10282)
|
||||
* Add readonly param to sequence, databaseId and collectionId in [#10278](https://github.com/appwrite/appwrite/pull/10278)
|
||||
* Update migrations in [#10283](https://github.com/appwrite/appwrite/pull/10283)
|
||||
* Add placeholder detection in [#10284](https://github.com/appwrite/appwrite/pull/10284)
|
||||
* Update docker base to 0.10.3 in [#10285](https://github.com/appwrite/appwrite/pull/10285)
|
||||
* Make check for adding warning header stricter in [#10293](https://github.com/appwrite/appwrite/pull/10293)
|
||||
* Fix databases worker cache clearing bug in [#10294](https://github.com/appwrite/appwrite/pull/10294)
|
||||
* Reapply Redis functions queue in [#10299](https://github.com/appwrite/appwrite/pull/10299)
|
||||
* Add new database query type tests in [#10296](https://github.com/appwrite/appwrite/pull/10296)
|
||||
* Update package in [#10312](https://github.com/appwrite/appwrite/pull/10312)
|
||||
* Update required attributes in [#10311](https://github.com/appwrite/appwrite/pull/10311)
|
||||
* Remove experiment warnings from bulk methods in [#10310](https://github.com/appwrite/appwrite/pull/10310)
|
||||
* Update README.md in [#10313](https://github.com/appwrite/appwrite/pull/10313)
|
||||
* Added internal file param to handle upload to internal bucket in [#10321](https://github.com/appwrite/appwrite/pull/10321)
|
||||
* Remove temp logging in [#10302](https://github.com/appwrite/appwrite/pull/10302)
|
||||
* Improve sites test for stability in [#10331](https://github.com/appwrite/appwrite/pull/10331)
|
||||
* Database lib bump to 0.71.15 in [#10336](https://github.com/appwrite/appwrite/pull/10336)
|
||||
* Clarify userId param in endpoints that create accounts in [#10117](https://github.com/appwrite/appwrite/pull/10117)
|
||||
* Upgrade HTTP in [#10338](https://github.com/appwrite/appwrite/pull/10338)
|
||||
* Remove unnessessary external dependnecies in [#10343](https://github.com/appwrite/appwrite/pull/10343)
|
||||
* Sync main into 1.7.x in [#10347](https://github.com/appwrite/appwrite/pull/10347)
|
||||
* Fix TablesDB casing in [#10346](https://github.com/appwrite/appwrite/pull/10346)
|
||||
* Add cookies test in [#10352](https://github.com/appwrite/appwrite/pull/10352)
|
||||
* Update token tests with jwt decode in [#10354](https://github.com/appwrite/appwrite/pull/10354)
|
||||
* Utilize assets server for fonts in [#10358](https://github.com/appwrite/appwrite/pull/10358)
|
||||
* Sync main into 1.7.x in [#10359](https://github.com/appwrite/appwrite/pull/10359)
|
||||
* Bump 1.7.x in [#10365](https://github.com/appwrite/appwrite/pull/10365)
|
||||
* Fix queue health in [#10369](https://github.com/appwrite/appwrite/pull/10369)
|
||||
* Allow publisher messaging override in scheduler in [#10370](https://github.com/appwrite/appwrite/pull/10370)
|
||||
* Add replacewith and deprecated since to account methods in [#10377](https://github.com/appwrite/appwrite/pull/10377)
|
||||
* Update README.md in [#10376](https://github.com/appwrite/appwrite/pull/10376)
|
||||
* Update CLI in [#10390](https://github.com/appwrite/appwrite/pull/10390)
|
||||
* Update default method in description in [#10391](https://github.com/appwrite/appwrite/pull/10391)
|
||||
* Rename namespace from tables-db to tablesdb in specs in [#10395](https://github.com/appwrite/appwrite/pull/10395)
|
||||
* Update tables group in specs in [#10394](https://github.com/appwrite/appwrite/pull/10394)
|
||||
* Update description for upsert methods in [#10397](https://github.com/appwrite/appwrite/pull/10397)
|
||||
* Update README.md in [#10401](https://github.com/appwrite/appwrite/pull/10401)
|
||||
* Added handling of database resources after migration in [#10400](https://github.com/appwrite/appwrite/pull/10400)
|
||||
* Revert "Added handling of database resources after migration" in [#10406](https://github.com/appwrite/appwrite/pull/10406)
|
||||
* Remove sdk deprecation warnings in [#10408](https://github.com/appwrite/appwrite/pull/10408)
|
||||
* Mark Row response model's param with readonly in [#10409](https://github.com/appwrite/appwrite/pull/10409)
|
||||
* Update exception thrown when svg sanitization fails in [#10416](https://github.com/appwrite/appwrite/pull/10416)
|
||||
* Fix allow null params in [#10417](https://github.com/appwrite/appwrite/pull/10417)
|
||||
* Allow running tests with specific response format in [#10418](https://github.com/appwrite/appwrite/pull/10418)
|
||||
* Make webhooks publisher overridable in [#10419](https://github.com/appwrite/appwrite/pull/10419)
|
||||
* Check audits logs in [#10414](https://github.com/appwrite/appwrite/pull/10414)
|
||||
* Remove direct publisher calls in [#10420](https://github.com/appwrite/appwrite/pull/10420)
|
||||
* removed spatial type response and will be using the json type for the… in [#10433](https://github.com/appwrite/appwrite/pull/10433)
|
||||
* Add tests for new time helpers in [#10437](https://github.com/appwrite/appwrite/pull/10437)
|
||||
* Move projects.list() to module in [#10441](https://github.com/appwrite/appwrite/pull/10441)
|
||||
* Update cli to 9.1.0 in [#10442](https://github.com/appwrite/appwrite/pull/10442)
|
||||
* Add requestBody param examples in specs in [#10431](https://github.com/appwrite/appwrite/pull/10431)
|
||||
* Fix mysql tests in [#10445](https://github.com/appwrite/appwrite/pull/10445)
|
||||
* Upgrade platform lib to have older queue lib in [#10447](https://github.com/appwrite/appwrite/pull/10447)
|
||||
* Fix router compression in [#10452](https://github.com/appwrite/appwrite/pull/10452)
|
||||
* Upgrade http lib for backwards compatible default param in [#10455](https://github.com/appwrite/appwrite/pull/10455)
|
||||
* Update examples in [#10444](https://github.com/appwrite/appwrite/pull/10444)
|
||||
* Automatic pr creation in sdk release script in [#10457](https://github.com/appwrite/appwrite/pull/10457)
|
||||
* Remove avatars command from cli in [#10454](https://github.com/appwrite/appwrite/pull/10454)
|
||||
* Remove deno from platforms array in [#10453](https://github.com/appwrite/appwrite/pull/10453)
|
||||
* Spatial type attributes sdk updates in [#10463](https://github.com/appwrite/appwrite/pull/10463)
|
||||
* Stats resources try catch in [#10469](https://github.com/appwrite/appwrite/pull/10469)
|
||||
* Move proxy endpoints to modules in [#10470](https://github.com/appwrite/appwrite/pull/10470)
|
||||
* Add certificate validation override in [#10471](https://github.com/appwrite/appwrite/pull/10471)
|
||||
* Generate SDKs in [#10475](https://github.com/appwrite/appwrite/pull/10475)
|
||||
* Spatial test tablesdb updates in [#10473](https://github.com/appwrite/appwrite/pull/10473)
|
||||
* Add colors to certificate logs in [#10438](https://github.com/appwrite/appwrite/pull/10438)
|
||||
* appwrite db bump in [#10479](https://github.com/appwrite/appwrite/pull/10479)
|
||||
* Bump database in [#10480](https://github.com/appwrite/appwrite/pull/10480)
|
||||
* Health db queues in [#10482](https://github.com/appwrite/appwrite/pull/10482)
|
||||
* Attempt small size for website dependency in [#10485](https://github.com/appwrite/appwrite/pull/10485)
|
||||
* Worker stop in [#10498](https://github.com/appwrite/appwrite/pull/10498)
|
||||
* Update database in [#10506](https://github.com/appwrite/appwrite/pull/10506)
|
||||
* Stats resources and usage sorting by unique field in [#10472](https://github.com/appwrite/appwrite/pull/10472)
|
||||
* Add spatial column validation during required mode and tests for exis… in [#10509](https://github.com/appwrite/appwrite/pull/10509)
|
||||
* Sub query variables order by in [#10513](https://github.com/appwrite/appwrite/pull/10513)
|
||||
* Update README.md in [#10514](https://github.com/appwrite/appwrite/pull/10514)
|
||||
* bump database 1.5.0 in [#10515](https://github.com/appwrite/appwrite/pull/10515)
|
||||
* Don't remove required attributes in [#10516](https://github.com/appwrite/appwrite/pull/10516)
|
||||
* Catch query exception on bulk update/delete in [#10517](https://github.com/appwrite/appwrite/pull/10517)
|
||||
* Update cli to 10.0.0 in [#10511](https://github.com/appwrite/appwrite/pull/10511)
|
||||
* Add type_enum support and update docs in [#10496](https://github.com/appwrite/appwrite/pull/10496)
|
||||
* Improve code readability for schedules in [#10522](https://github.com/appwrite/appwrite/pull/10522)
|
||||
* Include response model enum names in [#10538](https://github.com/appwrite/appwrite/pull/10538)
|
||||
* SDK releases in [#10539](https://github.com/appwrite/appwrite/pull/10539)
|
||||
* Fix health status enum in [#10540](https://github.com/appwrite/appwrite/pull/10540)
|
||||
* Update afterbuild fn in [#10541](https://github.com/appwrite/appwrite/pull/10541)
|
||||
* Update afterbuild to also pass adapter in [#10545](https://github.com/appwrite/appwrite/pull/10545)
|
||||
* Update `z-index` to be the highest in [#9874](https://github.com/appwrite/appwrite/pull/9874)
|
||||
* Update framework lib to 0.33.28 in [#10551](https://github.com/appwrite/appwrite/pull/10551)
|
||||
* Fix enum typing for platform in specs in [#10553](https://github.com/appwrite/appwrite/pull/10553)
|
||||
* Add enums for database type and column status in [#10561](https://github.com/appwrite/appwrite/pull/10561)
|
||||
* Fix activities in [#10586](https://github.com/appwrite/appwrite/pull/10586)
|
||||
* Fix logs truncation tests in [#10585](https://github.com/appwrite/appwrite/pull/10585)
|
||||
* Remove related data in realtime payload in [#10590](https://github.com/appwrite/appwrite/pull/10590)
|
||||
* Update composer dependencies in [#10601](https://github.com/appwrite/appwrite/pull/10601)
|
||||
* Update sdks add response models in [#10554](https://github.com/appwrite/appwrite/pull/10554)
|
||||
* Sanitize 5xx errors on realtime in [#10598](https://github.com/appwrite/appwrite/pull/10598)
|
||||
* Update database in [#10596](https://github.com/appwrite/appwrite/pull/10596)
|
||||
* Add both collection and table id in the realtime in [#10608](https://github.com/appwrite/appwrite/pull/10608)
|
||||
* Chore bump db in [#10611](https://github.com/appwrite/appwrite/pull/10611)
|
||||
* Branded email for Console auth flows in [#10501](https://github.com/appwrite/appwrite/pull/10501)
|
||||
* Add minor releases for all SDKs - deprecate createVerification, add createEmailVerification in [#10614](https://github.com/appwrite/appwrite/pull/10614)
|
||||
* Add automatic releases in [#10615](https://github.com/appwrite/appwrite/pull/10615)
|
||||
* Feat txn sdks in [#10621](https://github.com/appwrite/appwrite/pull/10621)
|
||||
* Prevent empty releases in sdk release script in [#10627](https://github.com/appwrite/appwrite/pull/10627)
|
||||
* Update domains lib to 0.8.2 in [#10629](https://github.com/appwrite/appwrite/pull/10629)
|
||||
* Fix txn API scope backwards compat in [#10640](https://github.com/appwrite/appwrite/pull/10640)
|
||||
* Fix block schedules in [#10620](https://github.com/appwrite/appwrite/pull/10620)
|
||||
* Update .NET SDK to 0.21.2 and improve release detection in [#10641](https://github.com/appwrite/appwrite/pull/10641)
|
||||
* Make methods protected for extending in [#10617](https://github.com/appwrite/appwrite/pull/10617)
|
||||
|
||||
# Version 1.7.4
|
||||
|
||||
## What's Changed
|
||||
|
||||
### Notable changes
|
||||
|
||||
* Update console image to version 6.0.13 in [#9891](https://github.com/appwrite/appwrite/pull/9891)
|
||||
|
||||
### Fixes
|
||||
|
||||
* Fix createDeployment chunk upload in [#9886](https://github.com/appwrite/appwrite/pull/9886)
|
||||
|
||||
### Miscellaneous
|
||||
|
||||
* Update version from 1.7.3 to 1.7.4 in [#9893](https://github.com/appwrite/appwrite/pull/9893)
|
||||
|
||||
# Version 1.7.3
|
||||
|
||||
## What's Changed
|
||||
|
||||
### Notable changes
|
||||
|
||||
* Allow unlimited deployment size in [#9866](https://github.com/appwrite/appwrite/pull/9866)
|
||||
* Bump console to version 6.0.11 in [#9881](https://github.com/appwrite/appwrite/pull/9881)
|
||||
|
||||
### Fixes
|
||||
|
||||
* Send deploymentResourceType in rules verification in [#9859](https://github.com/appwrite/appwrite/pull/9859)
|
||||
* Fix CNAME validation in [#9861](https://github.com/appwrite/appwrite/pull/9861)
|
||||
* Fix bucket not included in path in [#9864](https://github.com/appwrite/appwrite/pull/9864)
|
||||
* Fix URL for view logs in github comment in [#9875](https://github.com/appwrite/appwrite/pull/9875)
|
||||
* Set owner and region while migrating rules in [#9856](https://github.com/appwrite/appwrite/pull/9856)
|
||||
* Remove _APP_DEFAULT_REGION because it is not a valid env var in [#9883](https://github.com/appwrite/appwrite/pull/9883)
|
||||
|
||||
### Miscellaneous
|
||||
|
||||
* Only load error page for development mode in [#9860](https://github.com/appwrite/appwrite/pull/9860)
|
||||
* Make max deployment and build size configurable in [#9863](https://github.com/appwrite/appwrite/pull/9863)
|
||||
* Update flutter_web_auth_2 docs to match 4.x in [#9858](https://github.com/appwrite/appwrite/pull/9858)
|
||||
* Use unique filename for health check in [#9842](https://github.com/appwrite/appwrite/pull/9842)
|
||||
* Added encrypt property in the attribute string response model in [#9868](https://github.com/appwrite/appwrite/pull/9868)
|
||||
* Add sequence in [#9865](https://github.com/appwrite/appwrite/pull/9865)
|
||||
* Add builds worker group in [#9873](https://github.com/appwrite/appwrite/pull/9873)
|
||||
* updated errro for the string encryption in [#9878](https://github.com/appwrite/appwrite/pull/9878)
|
||||
* Revert "Add sequence" in [#9879](https://github.com/appwrite/appwrite/pull/9879)
|
||||
* Prepare 1.7.3 release in [#9882](https://github.com/appwrite/appwrite/pull/9882)
|
||||
|
||||
# Version 1.6.2
|
||||
|
||||
## What's Changed
|
||||
|
||||
### Notable changes
|
||||
|
||||
* Delete git folder to reduce build size in [9076](https://github.com/appwrite/appwrite/pull/9076)
|
||||
* Upgrade assistant in [9100](https://github.com/appwrite/appwrite/pull/9100)
|
||||
* Use redis adapter for abuse in [9121](https://github.com/appwrite/appwrite/pull/9121)
|
||||
* Set base specification CPUs to 0.5 again in [9146](https://github.com/appwrite/appwrite/pull/9146)
|
||||
* Add new push message parameters in [9060](https://github.com/appwrite/appwrite/pull/9060)
|
||||
* Update audits to include user type in [9211](https://github.com/appwrite/appwrite/pull/9211)
|
||||
* Enable HEIC in [9251](https://github.com/appwrite/appwrite/pull/9251)
|
||||
* Added teamName to membership redirect url in [9269](https://github.com/appwrite/appwrite/pull/9269)
|
||||
* Add support endpoint url for S3 in [9303](https://github.com/appwrite/appwrite/pull/9303)
|
||||
* Added RuPay Credit Card Icon in Avatars Service in [5046](https://github.com/appwrite/appwrite/pull/5046)
|
||||
* Add figma oauth provider in [9623](https://github.com/appwrite/appwrite/pull/9623)
|
||||
* Update console to version 5.2.58 in [9637](https://github.com/appwrite/appwrite/pull/9637)
|
||||
* Delete git folder to reduce build size in [#9076](https://github.com/appwrite/appwrite/pull/9076)
|
||||
* Upgrade assistant in [#9100](https://github.com/appwrite/appwrite/pull/9100)
|
||||
* Use redis adapter for abuse in [#9121](https://github.com/appwrite/appwrite/pull/9121)
|
||||
* Set base specification CPUs to 0.5 again in [#9146](https://github.com/appwrite/appwrite/pull/9146)
|
||||
* Add new push message parameters in [#9060](https://github.com/appwrite/appwrite/pull/9060)
|
||||
* Update audits to include user type in [#9211](https://github.com/appwrite/appwrite/pull/9211)
|
||||
* Enable HEIC in [#9251](https://github.com/appwrite/appwrite/pull/9251)
|
||||
* Added teamName to membership redirect url in [#9269](https://github.com/appwrite/appwrite/pull/9269)
|
||||
* Add support endpoint url for S3 in [#9303](https://github.com/appwrite/appwrite/pull/9303)
|
||||
* Added RuPay Credit Card Icon in Avatars Service in [#5046](https://github.com/appwrite/appwrite/pull/5046)
|
||||
* Add figma oauth provider in [#9623](https://github.com/appwrite/appwrite/pull/9623)
|
||||
* Update console to version 5.2.58 in [#9637](https://github.com/appwrite/appwrite/pull/9637)
|
||||
|
||||
### Fixes
|
||||
|
||||
* Remove failed attribute in [9032](https://github.com/appwrite/appwrite/pull/9032)
|
||||
* Fix delete notFound attribute in [9038](https://github.com/appwrite/appwrite/pull/9038)
|
||||
* 🇮🇸 Added missing Icelandic translations for email strings. in [4848](https://github.com/appwrite/appwrite/pull/4848)
|
||||
* fix doc comment for filter method in [5769](https://github.com/appwrite/appwrite/pull/5769)
|
||||
* Delete attribute No throwing Exception on not found in [9157](https://github.com/appwrite/appwrite/pull/9157)
|
||||
* Fix VCS identity collision in [9138](https://github.com/appwrite/appwrite/pull/9138)
|
||||
* Fix disabling of email-otp when user wants to in [9200](https://github.com/appwrite/appwrite/pull/9200)
|
||||
* Ensure user can delete session in [9209](https://github.com/appwrite/appwrite/pull/9209)
|
||||
* Fix resend invitation in [9218](https://github.com/appwrite/appwrite/pull/9218)
|
||||
* Fix phone number parsing exception handling in [9246](https://github.com/appwrite/appwrite/pull/9246)
|
||||
* Fix amazon oauth in [9253](https://github.com/appwrite/appwrite/pull/9253)
|
||||
* Fix slack oauth scopes, and updated to v2 in [9228](https://github.com/appwrite/appwrite/pull/9228)
|
||||
* Fix forwarded user agent in [9271](https://github.com/appwrite/appwrite/pull/9271)
|
||||
* Fix WEBP File Preview Rendering Issue in [9321](https://github.com/appwrite/appwrite/pull/9321)
|
||||
* Fix build memory specifications in [9360](https://github.com/appwrite/appwrite/pull/9360)
|
||||
* Fix Self Hosting functions by adding missed config in [9373](https://github.com/appwrite/appwrite/pull/9373)
|
||||
* Fix resend team invite if already accepted in [9348](https://github.com/appwrite/appwrite/pull/9348)
|
||||
* Fix null errors on team invite in [9391](https://github.com/appwrite/appwrite/pull/9391)
|
||||
* Fix email (smtp) to multiple recipients in [9243](https://github.com/appwrite/appwrite/pull/9243)
|
||||
* Fix stats timing by using receivedAt date when available in [9428](https://github.com/appwrite/appwrite/pull/9428)
|
||||
* Make min/max params optional for attribute update in [9387](https://github.com/appwrite/appwrite/pull/9387)
|
||||
* Fix blocking of phone sessions when disabled on console in [9447](https://github.com/appwrite/appwrite/pull/9447)
|
||||
* Fix logging config in [9467](https://github.com/appwrite/appwrite/pull/9467)
|
||||
* Update audit timestamp origin in [9481](https://github.com/appwrite/appwrite/pull/9481)
|
||||
* Fix certificates in deletes worker in [9466](https://github.com/appwrite/appwrite/pull/9466)
|
||||
* Fix console audits delete in [9547](https://github.com/appwrite/appwrite/pull/9547)
|
||||
* Fix migrations in [9633](https://github.com/appwrite/appwrite/pull/9633)
|
||||
* Ensure all 4xx errors in OAuth redirect lead to the failure URL in [9679](https://github.com/appwrite/appwrite/pull/9679)
|
||||
* Treat 0 as unlimited for CPUs and memory in [9638](https://github.com/appwrite/appwrite/pull/9638)
|
||||
* Add contextual dispatch logic to fix high CPU usage in [9687](https://github.com/appwrite/appwrite/pull/9687)
|
||||
* Remove failed attribute in [#9032](https://github.com/appwrite/appwrite/pull/9032)
|
||||
* Fix delete notFound attribute in [#9038](https://github.com/appwrite/appwrite/pull/9038)
|
||||
* 🇮🇸 Added missing Icelandic translations for email strings. in [#4848](https://github.com/appwrite/appwrite/pull/4848)
|
||||
* fix doc comment for filter method in [#5769](https://github.com/appwrite/appwrite/pull/5769)
|
||||
* Delete attribute No throwing Exception on not found in [#9157](https://github.com/appwrite/appwrite/pull/9157)
|
||||
* Fix VCS identity collision in [#9138](https://github.com/appwrite/appwrite/pull/9138)
|
||||
* Fix disabling of email-otp when user wants to in [#9200](https://github.com/appwrite/appwrite/pull/9200)
|
||||
* Ensure user can delete session in [#9209](https://github.com/appwrite/appwrite/pull/9209)
|
||||
* Fix resend invitation in [#9218](https://github.com/appwrite/appwrite/pull/9218)
|
||||
* Fix phone number parsing exception handling in [#9246](https://github.com/appwrite/appwrite/pull/9246)
|
||||
* Fix amazon oauth in [#9253](https://github.com/appwrite/appwrite/pull/9253)
|
||||
* Fix slack oauth scopes, and updated to v2 in [#9228](https://github.com/appwrite/appwrite/pull/9228)
|
||||
* Fix forwarded user agent in [#9271](https://github.com/appwrite/appwrite/pull/9271)
|
||||
* Fix WEBP File Preview Rendering Issue in [#9321](https://github.com/appwrite/appwrite/pull/9321)
|
||||
* Fix build memory specifications in [#9360](https://github.com/appwrite/appwrite/pull/9360)
|
||||
* Fix Self Hosting functions by adding missed config in [#9373](https://github.com/appwrite/appwrite/pull/9373)
|
||||
* Fix resend team invite if already accepted in [#9348](https://github.com/appwrite/appwrite/pull/9348)
|
||||
* Fix null errors on team invite in [#9391](https://github.com/appwrite/appwrite/pull/9391)
|
||||
* Fix email (smtp) to multiple recipients in [#9243](https://github.com/appwrite/appwrite/pull/9243)
|
||||
* Fix stats timing by using receivedAt date when available in [#9428](https://github.com/appwrite/appwrite/pull/9428)
|
||||
* Make min/max params optional for attribute update in [#9387](https://github.com/appwrite/appwrite/pull/9387)
|
||||
* Fix blocking of phone sessions when disabled on console in [#9447](https://github.com/appwrite/appwrite/pull/9447)
|
||||
* Fix logging config in [#9467](https://github.com/appwrite/appwrite/pull/9467)
|
||||
* Update audit timestamp origin in [#9481](https://github.com/appwrite/appwrite/pull/9481)
|
||||
* Fix certificates in deletes worker in [#9466](https://github.com/appwrite/appwrite/pull/9466)
|
||||
* Fix console audits delete in [#9547](https://github.com/appwrite/appwrite/pull/9547)
|
||||
* Fix migrations in [#9633](https://github.com/appwrite/appwrite/pull/9633)
|
||||
* Ensure all 4xx errors in OAuth redirect lead to the failure URL in [#9679](https://github.com/appwrite/appwrite/pull/9679)
|
||||
* Treat 0 as unlimited for CPUs and memory in [#9638](https://github.com/appwrite/appwrite/pull/9638)
|
||||
* Add contextual dispatch logic to fix high CPU usage in [#9687](https://github.com/appwrite/appwrite/pull/9687)
|
||||
|
||||
### Miscellaneous
|
||||
|
||||
* Merge 1.6.x into feat-custom-cf-hostnames in [8904](https://github.com/appwrite/appwrite/pull/8904)
|
||||
* Improve compression param checks in [8922](https://github.com/appwrite/appwrite/pull/8922)
|
||||
* upgrade utopia storage in [8930](https://github.com/appwrite/appwrite/pull/8930)
|
||||
* Feat migration in [8797](https://github.com/appwrite/appwrite/pull/8797)
|
||||
* feat fix web routes in [8962](https://github.com/appwrite/appwrite/pull/8962)
|
||||
* Fix no pool access in [9027](https://github.com/appwrite/appwrite/pull/9027)
|
||||
* feat: use environment variable to check rules format in [9039](https://github.com/appwrite/appwrite/pull/9039)
|
||||
* Update storage.php in [9037](https://github.com/appwrite/appwrite/pull/9037)
|
||||
* Upgrade db 0.53.200 in [9050](https://github.com/appwrite/appwrite/pull/9050)
|
||||
* Chore: upgrade utopia storage in [9066](https://github.com/appwrite/appwrite/pull/9066)
|
||||
* Update usage-dump payload in [9085](https://github.com/appwrite/appwrite/pull/9085)
|
||||
* GitHub Workflows security hardening in [3728](https://github.com/appwrite/appwrite/pull/3728)
|
||||
* Update add-oauth2-provider.md in [4313](https://github.com/appwrite/appwrite/pull/4313)
|
||||
* update readme-cn some doc in [5278](https://github.com/appwrite/appwrite/pull/5278)
|
||||
* Add accessibility features in [7042](https://github.com/appwrite/appwrite/pull/7042)
|
||||
* Add Appwrite Cloud to read me. in [5445](https://github.com/appwrite/appwrite/pull/5445)
|
||||
* Migration throw error in [9092](https://github.com/appwrite/appwrite/pull/9092)
|
||||
* Fix usage payload bug in [9097](https://github.com/appwrite/appwrite/pull/9097)
|
||||
* chore: replace occurrences of dbForConsole to dbForPlatform in [9096](https://github.com/appwrite/appwrite/pull/9096)
|
||||
* fix(realtime): decrement connectionCounter only if connection is known in [9055](https://github.com/appwrite/appwrite/pull/9055)
|
||||
* payload bug fix in [9098](https://github.com/appwrite/appwrite/pull/9098)
|
||||
* Fix usage payload bug in [9099](https://github.com/appwrite/appwrite/pull/9099)
|
||||
* Usage payload debug in [9101](https://github.com/appwrite/appwrite/pull/9101)
|
||||
* Usage payload debug in [9103](https://github.com/appwrite/appwrite/pull/9103)
|
||||
* Usage payload debug in [9104](https://github.com/appwrite/appwrite/pull/9104)
|
||||
* Feat: createFunction abuse labels in [9102](https://github.com/appwrite/appwrite/pull/9102)
|
||||
* Docs-create-document in [9105](https://github.com/appwrite/appwrite/pull/9105)
|
||||
* Docs: Create document and unknown attribute error messages. in [5427](https://github.com/appwrite/appwrite/pull/5427)
|
||||
* Fix: update project accessed at from router and schedulers in [9109](https://github.com/appwrite/appwrite/pull/9109)
|
||||
* chore: initial commit in [9111](https://github.com/appwrite/appwrite/pull/9111)
|
||||
* chore: optimise webhooks payload in [9115](https://github.com/appwrite/appwrite/pull/9115)
|
||||
* Revert "chore: initial commit" in [9117](https://github.com/appwrite/appwrite/pull/9117)
|
||||
* chore: fix attribute name in [9118](https://github.com/appwrite/appwrite/pull/9118)
|
||||
* Migrate to redis abuse in [9124](https://github.com/appwrite/appwrite/pull/9124)
|
||||
* Added webhooks usage stats in [9125](https://github.com/appwrite/appwrite/pull/9125)
|
||||
* chore remove abuse cleanup in [9137](https://github.com/appwrite/appwrite/pull/9137)
|
||||
* fix: remove abuse delete trigger in [9139](https://github.com/appwrite/appwrite/pull/9139)
|
||||
* Remove firebase OAuth API endpoints in [9144](https://github.com/appwrite/appwrite/pull/9144)
|
||||
* chore: release client sdks in [9112](https://github.com/appwrite/appwrite/pull/9112)
|
||||
* Update general.php in [9155](https://github.com/appwrite/appwrite/pull/9155)
|
||||
* feat(swoole): allow configuration override of available cpus in [9177](https://github.com/appwrite/appwrite/pull/9177)
|
||||
* Usage databases api read writes addition in [9142](https://github.com/appwrite/appwrite/pull/9142)
|
||||
* Fix dead connections in [9190](https://github.com/appwrite/appwrite/pull/9190)
|
||||
* Add hostname to audits in [9165](https://github.com/appwrite/appwrite/pull/9165)
|
||||
* chore: shifted authphone usage tracking to api calls in [9191](https://github.com/appwrite/appwrite/pull/9191)
|
||||
* Revert "Fix dead connections" in [9201](https://github.com/appwrite/appwrite/pull/9201)
|
||||
* Add assertEventually to messaging provider logs test in [9192](https://github.com/appwrite/appwrite/pull/9192)
|
||||
* feat project sms usage in [9198](https://github.com/appwrite/appwrite/pull/9198)
|
||||
* chore: add audit labels to project resources in [9056](https://github.com/appwrite/appwrite/pull/9056)
|
||||
* fix sms usage in [9207](https://github.com/appwrite/appwrite/pull/9207)
|
||||
* Update database in [9202](https://github.com/appwrite/appwrite/pull/9202)
|
||||
* Fix dead connections in [9213](https://github.com/appwrite/appwrite/pull/9213)
|
||||
* Revert "Fix dead connections" in [9214](https://github.com/appwrite/appwrite/pull/9214)
|
||||
* Add logs db init for consistency in [9163](https://github.com/appwrite/appwrite/pull/9163)
|
||||
* Split the collection definitions in [9153](https://github.com/appwrite/appwrite/pull/9153)
|
||||
* Log path with populated parameters in [9220](https://github.com/appwrite/appwrite/pull/9220)
|
||||
* Add missing scope on function template in [9208](https://github.com/appwrite/appwrite/pull/9208)
|
||||
* Add relatedCollection default in [9225](https://github.com/appwrite/appwrite/pull/9225)
|
||||
* fix: function usage in [9235](https://github.com/appwrite/appwrite/pull/9235)
|
||||
* feat: optimise events payloads in [9232](https://github.com/appwrite/appwrite/pull/9232)
|
||||
* Optimise webhook events in [9168](https://github.com/appwrite/appwrite/pull/9168)
|
||||
* fix: maintenance job missing type in [9238](https://github.com/appwrite/appwrite/pull/9238)
|
||||
* Update Fetch to 0.3.0 in [9245](https://github.com/appwrite/appwrite/pull/9245)
|
||||
* Fix maintenance job in [9247](https://github.com/appwrite/appwrite/pull/9247)
|
||||
* chore: add missing case for executions in [9248](https://github.com/appwrite/appwrite/pull/9248)
|
||||
* Add index dependency exception in [9226](https://github.com/appwrite/appwrite/pull/9226)
|
||||
* chore: fix benchmarking test when made from fork in [9233](https://github.com/appwrite/appwrite/pull/9233)
|
||||
* Update SDK Generator versions in [9188](https://github.com/appwrite/appwrite/pull/9188)
|
||||
* chore: skipped job instead of throwing error in [9250](https://github.com/appwrite/appwrite/pull/9250)
|
||||
* Implement new SDK Class on 1.6.x in [9237](https://github.com/appwrite/appwrite/pull/9237)
|
||||
* Delete collection before Appwrite's attributes in [9256](https://github.com/appwrite/appwrite/pull/9256)
|
||||
* Feat batch usage dump in [9255](https://github.com/appwrite/appwrite/pull/9255)
|
||||
* Fix cloud tests in [9261](https://github.com/appwrite/appwrite/pull/9261)
|
||||
* Usage: Databases reads writes in [9260](https://github.com/appwrite/appwrite/pull/9260)
|
||||
* Update: Latest sdk specs in [9274](https://github.com/appwrite/appwrite/pull/9274)
|
||||
* Revert "Feat batch usage dump" in [9276](https://github.com/appwrite/appwrite/pull/9276)
|
||||
* feat: add fast2SMS adapter in [9263](https://github.com/appwrite/appwrite/pull/9263)
|
||||
* Update Sdk Generator dependency in [9280](https://github.com/appwrite/appwrite/pull/9280)
|
||||
* Transformed at addition in [9281](https://github.com/appwrite/appwrite/pull/9281)
|
||||
* Docs: clarify update endpoints only work on draft messages in [9236](https://github.com/appwrite/appwrite/pull/9236)
|
||||
* Update sdk generator dependency in [9282](https://github.com/appwrite/appwrite/pull/9282)
|
||||
* Revert "Transformed at addition" in [9284](https://github.com/appwrite/appwrite/pull/9284)
|
||||
* replaced init for cloud link in [9285](https://github.com/appwrite/appwrite/pull/9285)
|
||||
* Add transformed at in [9289](https://github.com/appwrite/appwrite/pull/9289)
|
||||
* Make migrations use Dynamic keys for destination in [9291](https://github.com/appwrite/appwrite/pull/9291)
|
||||
* Make sessions limit tests assert eventually in [9298](https://github.com/appwrite/appwrite/pull/9298)
|
||||
* Chore update database in [9306](https://github.com/appwrite/appwrite/pull/9306)
|
||||
* feat: add AMQP queues in [9287](https://github.com/appwrite/appwrite/pull/9287)
|
||||
* fix(test): use assertEventually instead of while(true) in [9308](https://github.com/appwrite/appwrite/pull/9308)
|
||||
* fix(certificate worker): events are published without queue name in [9309](https://github.com/appwrite/appwrite/pull/9309)
|
||||
* chore: update utopia-php/queue to 0.8.1 in [9311](https://github.com/appwrite/appwrite/pull/9311)
|
||||
* chore: update utopia-php/queue to 0.8.2 in [9312](https://github.com/appwrite/appwrite/pull/9312)
|
||||
* fix(schedule-tasks): revert back to direct pool usage in [9313](https://github.com/appwrite/appwrite/pull/9313)
|
||||
* feat: custom app schemes in [9262](https://github.com/appwrite/appwrite/pull/9262)
|
||||
* Revert "feat: custom app schemes" in [9319](https://github.com/appwrite/appwrite/pull/9319)
|
||||
* Restore "feat: custom app schemes"" in [9320](https://github.com/appwrite/appwrite/pull/9320)
|
||||
* Revert "Restore "feat: custom app schemes""" in [9323](https://github.com/appwrite/appwrite/pull/9323)
|
||||
* chore: update dependencies in [9330](https://github.com/appwrite/appwrite/pull/9330)
|
||||
* Feat: logs DB in [9272](https://github.com/appwrite/appwrite/pull/9272)
|
||||
* Catch invalid index in [9329](https://github.com/appwrite/appwrite/pull/9329)
|
||||
* Fix: missing call for image transformations counting in [9342](https://github.com/appwrite/appwrite/pull/9342)
|
||||
* Fix drop abuse on shared table project delete in [9346](https://github.com/appwrite/appwrite/pull/9346)
|
||||
* Only run all table mode tests on db update in [9338](https://github.com/appwrite/appwrite/pull/9338)
|
||||
* Fix: missing periodic metric in [9350](https://github.com/appwrite/appwrite/pull/9350)
|
||||
* feat(builds): check if function is blocked before building in [9332](https://github.com/appwrite/appwrite/pull/9332)
|
||||
* feat: batch create audit logs in [9347](https://github.com/appwrite/appwrite/pull/9347)
|
||||
* Chore: Update migrations in [9355](https://github.com/appwrite/appwrite/pull/9355)
|
||||
* Fix: metric time was not being written to DB in [9354](https://github.com/appwrite/appwrite/pull/9354)
|
||||
* Fix patch index validation in [9356](https://github.com/appwrite/appwrite/pull/9356)
|
||||
* Fix image trnasformation metrics in [9370](https://github.com/appwrite/appwrite/pull/9370)
|
||||
* Use batch delete in worker in [9375](https://github.com/appwrite/appwrite/pull/9375)
|
||||
* Fix Model Platform is missing response key: store in [9361](https://github.com/appwrite/appwrite/pull/9361)
|
||||
* Feat key segmented usage in [9336](https://github.com/appwrite/appwrite/pull/9336)
|
||||
* Feat messaging metrics in [9353](https://github.com/appwrite/appwrite/pull/9353)
|
||||
* Fix removed audits for shared v2 in [9388](https://github.com/appwrite/appwrite/pull/9388)
|
||||
* chore: bump utopia-php/image to 0.8.0 in [9390](https://github.com/appwrite/appwrite/pull/9390)
|
||||
* Fix outdated CLI commands in documentation in [9122](https://github.com/appwrite/appwrite/pull/9122)
|
||||
* disable logs display in [9398](https://github.com/appwrite/appwrite/pull/9398)
|
||||
* Log batches per project in [9403](https://github.com/appwrite/appwrite/pull/9403)
|
||||
* Batch per project in [9410](https://github.com/appwrite/appwrite/pull/9410)
|
||||
* Fix: stats resources only queue projects accessed in last 3 hours in [9411](https://github.com/appwrite/appwrite/pull/9411)
|
||||
* Track options requests in [9397](https://github.com/appwrite/appwrite/pull/9397)
|
||||
* chore: bump docker-base in [9406](https://github.com/appwrite/appwrite/pull/9406)
|
||||
* refactor: migrate Realtime::send calls to queueForRealtime in [9325](https://github.com/appwrite/appwrite/pull/9325)
|
||||
* Revert "Fix: stats resources only queue projects accessed in last 3 hours" in [9424](https://github.com/appwrite/appwrite/pull/9424)
|
||||
* Remove usage and usage dump in favor of stats-usage and stats-usage-dump in [9339](https://github.com/appwrite/appwrite/pull/9339)
|
||||
* Fix: disable dual writing in [9429](https://github.com/appwrite/appwrite/pull/9429)
|
||||
* Disable transformedAt update for console users in [9425](https://github.com/appwrite/appwrite/pull/9425)
|
||||
* chore: add image transformation stats to usage endpoint in [9393](https://github.com/appwrite/appwrite/pull/9393)
|
||||
* chore: added timeout to deployment builds in tests in [9426](https://github.com/appwrite/appwrite/pull/9426)
|
||||
* fix: model for image transformations in usage project in [9442](https://github.com/appwrite/appwrite/pull/9442)
|
||||
* Feat: calculate database storage in stats-resources in [9443](https://github.com/appwrite/appwrite/pull/9443)
|
||||
* Activities batch writes in [9438](https://github.com/appwrite/appwrite/pull/9438)
|
||||
* chore: bump cache 0.12.x in [9412](https://github.com/appwrite/appwrite/pull/9412)
|
||||
* chore: queue console project for maintenance delete in [9479](https://github.com/appwrite/appwrite/pull/9479)
|
||||
* chore: added logsdb for deletes worker in [9462](https://github.com/appwrite/appwrite/pull/9462)
|
||||
* Feat: calculate and log time taken for each project in [9491](https://github.com/appwrite/appwrite/pull/9491)
|
||||
* chore: update initializing dbForLogs in [9494](https://github.com/appwrite/appwrite/pull/9494)
|
||||
* Feat bulk audit delete in [9487](https://github.com/appwrite/appwrite/pull/9487)
|
||||
* Prepare 1.6.2 release in [9499](https://github.com/appwrite/appwrite/pull/9499)
|
||||
* Regenerate specs in [9497](https://github.com/appwrite/appwrite/pull/9497)
|
||||
* Regenerate examples in [9498](https://github.com/appwrite/appwrite/pull/9498)
|
||||
* chore: bump sdk in [9414](https://github.com/appwrite/appwrite/pull/9414)
|
||||
* update queue to 0.9.* in [9505](https://github.com/appwrite/appwrite/pull/9505)
|
||||
* Feat improve delete queries in [9507](https://github.com/appwrite/appwrite/pull/9507)
|
||||
* Feat: Add rule attributes in [9508](https://github.com/appwrite/appwrite/pull/9508)
|
||||
* Sync main into 1.6.x in [9496](https://github.com/appwrite/appwrite/pull/9496)
|
||||
* Bump console to version 5.2.53 in [9495](https://github.com/appwrite/appwrite/pull/9495)
|
||||
* Prepare 1.6.1 release in [9294](https://github.com/appwrite/appwrite/pull/9294)
|
||||
* Improve delete ordering in [9512](https://github.com/appwrite/appwrite/pull/9512)
|
||||
* Cleanups in [9511](https://github.com/appwrite/appwrite/pull/9511)
|
||||
* Feat dynamic regions in [9408](https://github.com/appwrite/appwrite/pull/9408)
|
||||
* Feat env vars to system lib in [9515](https://github.com/appwrite/appwrite/pull/9515)
|
||||
* Feat: domains count in [9514](https://github.com/appwrite/appwrite/pull/9514)
|
||||
* Migration read from db in [9529](https://github.com/appwrite/appwrite/pull/9529)
|
||||
* feat: add pool telemetry in [9530](https://github.com/appwrite/appwrite/pull/9530)
|
||||
* Disable PDO persistence since we manage our own pool in [9526](https://github.com/appwrite/appwrite/pull/9526)
|
||||
* chore: set min operations to 1 for reads and writes in [9536](https://github.com/appwrite/appwrite/pull/9536)
|
||||
* Remove default region in [9430](https://github.com/appwrite/appwrite/pull/9430)
|
||||
* Use cursor pagination with bigger limit for maintenance project loop in [9546](https://github.com/appwrite/appwrite/pull/9546)
|
||||
* chore: stop tests on failure in [9525](https://github.com/appwrite/appwrite/pull/9525)
|
||||
* chore: only update total count for privileged users in [9554](https://github.com/appwrite/appwrite/pull/9554)
|
||||
* refactor: initialization of audit retention in [9563](https://github.com/appwrite/appwrite/pull/9563)
|
||||
* Delete worker queries fixes in [9523](https://github.com/appwrite/appwrite/pull/9523)
|
||||
* Bump database 0.62.x in [9568](https://github.com/appwrite/appwrite/pull/9568)
|
||||
* Fix: schedules region filtering in [9577](https://github.com/appwrite/appwrite/pull/9577)
|
||||
* Deletes worker fix selects for pagination in [9578](https://github.com/appwrite/appwrite/pull/9578)
|
||||
* Add $permissions for delete documents selects in [9579](https://github.com/appwrite/appwrite/pull/9579)
|
||||
* chore(audits): return queue pre-fetch results in [9533](https://github.com/appwrite/appwrite/pull/9533)
|
||||
* Revert "chore(audits): return queue pre-fetch results" in [9586](https://github.com/appwrite/appwrite/pull/9586)
|
||||
* Feat multi tenant insert in [9573](https://github.com/appwrite/appwrite/pull/9573)
|
||||
* Add order by for cursor in [9588](https://github.com/appwrite/appwrite/pull/9588)
|
||||
* Feat update fetch in [9592](https://github.com/appwrite/appwrite/pull/9592)
|
||||
* Fix tenant casting in [9598](https://github.com/appwrite/appwrite/pull/9598)
|
||||
* Feat update ws in [9602](https://github.com/appwrite/appwrite/pull/9602)
|
||||
* Update database in [9603](https://github.com/appwrite/appwrite/pull/9603)
|
||||
* Fix: image transformation cache in [9608](https://github.com/appwrite/appwrite/pull/9608)
|
||||
* Remove audit payload in [9610](https://github.com/appwrite/appwrite/pull/9610)
|
||||
* Sample rate from DSN in [9559](https://github.com/appwrite/appwrite/pull/9559)
|
||||
* Restrict role change for sole org owner in [9615](https://github.com/appwrite/appwrite/pull/9615)
|
||||
* chore: update php image to 0.8.1 in [9616](https://github.com/appwrite/appwrite/pull/9616)
|
||||
* feat: refactor executor setup in [9420](https://github.com/appwrite/appwrite/pull/9420)
|
||||
* chore: update gitpod.yml config in [9561](https://github.com/appwrite/appwrite/pull/9561)
|
||||
* chore: update dependencies in [9625](https://github.com/appwrite/appwrite/pull/9625)
|
||||
* Update migrations lib in [9628](https://github.com/appwrite/appwrite/pull/9628)
|
||||
* feat: cache telemetry in [9624](https://github.com/appwrite/appwrite/pull/9624)
|
||||
* Bump console to version 5.2.56 in [9631](https://github.com/appwrite/appwrite/pull/9631)
|
||||
* Multi region support in [8667](https://github.com/appwrite/appwrite/pull/8667)
|
||||
* Revert "Multi region support" in [9632](https://github.com/appwrite/appwrite/pull/9632)
|
||||
* Revert "Revert "Multi region support"" in [9636](https://github.com/appwrite/appwrite/pull/9636)
|
||||
* Fix tasks in [9644](https://github.com/appwrite/appwrite/pull/9644)
|
||||
* chore: updated the migration version to 8.6 in [9646](https://github.com/appwrite/appwrite/pull/9646)
|
||||
* Fix: merge the working of StatsUsage and StatsUsageDump in [9585](https://github.com/appwrite/appwrite/pull/9585)
|
||||
* Update database in [9643](https://github.com/appwrite/appwrite/pull/9643)
|
||||
* chore: fix error logging for CLI tasks in [9651](https://github.com/appwrite/appwrite/pull/9651)
|
||||
* fix: usage test assertion in [9653](https://github.com/appwrite/appwrite/pull/9653)
|
||||
* Fix keys in [9656](https://github.com/appwrite/appwrite/pull/9656)
|
||||
* Feat: multi tenant dual writing in [9583](https://github.com/appwrite/appwrite/pull/9583)
|
||||
* Fix/throwing 400 for null order attributes in [9657](https://github.com/appwrite/appwrite/pull/9657)
|
||||
* feat: sdk group attribute in [9596](https://github.com/appwrite/appwrite/pull/9596)
|
||||
* Add configurable function and build size in [9648](https://github.com/appwrite/appwrite/pull/9648)
|
||||
* feat: update API endpoint in the code examples in [8933](https://github.com/appwrite/appwrite/pull/8933)
|
||||
* chore: abstract token secret hiding to response model in [9574](https://github.com/appwrite/appwrite/pull/9574)
|
||||
* chore: update sdks in [9655](https://github.com/appwrite/appwrite/pull/9655)
|
||||
* feat: allow non-critical events to ignore exceptions when enqueuing the event in [9680](https://github.com/appwrite/appwrite/pull/9680)
|
||||
* Revert "Add configurable function and build size" in [9681](https://github.com/appwrite/appwrite/pull/9681)
|
||||
* core: introduce endpoint.docs in specs in [9685](https://github.com/appwrite/appwrite/pull/9685)
|
||||
* fix: remove content-type header from get request specs in [9666](https://github.com/appwrite/appwrite/pull/9666)
|
||||
* chore: update flutter sdk in [9691](https://github.com/appwrite/appwrite/pull/9691)
|
||||
* Merge 1.6.x into feat-custom-cf-hostnames in [#8904](https://github.com/appwrite/appwrite/pull/8904)
|
||||
* Improve compression param checks in [#8922](https://github.com/appwrite/appwrite/pull/8922)
|
||||
* upgrade utopia storage in [#8930](https://github.com/appwrite/appwrite/pull/8930)
|
||||
* Feat migration in [#8797](https://github.com/appwrite/appwrite/pull/8797)
|
||||
* feat fix web routes in [#8962](https://github.com/appwrite/appwrite/pull/8962)
|
||||
* Fix no pool access in [#9027](https://github.com/appwrite/appwrite/pull/9027)
|
||||
* feat: use environment variable to check rules format in [#9039](https://github.com/appwrite/appwrite/pull/9039)
|
||||
* Update storage.php in [#9037](https://github.com/appwrite/appwrite/pull/9037)
|
||||
* Upgrade db 0.53.200 in [#9050](https://github.com/appwrite/appwrite/pull/9050)
|
||||
* Chore: upgrade utopia storage in [#9066](https://github.com/appwrite/appwrite/pull/9066)
|
||||
* Update usage-dump payload in [#9085](https://github.com/appwrite/appwrite/pull/9085)
|
||||
* GitHub Workflows security hardening in [#3728](https://github.com/appwrite/appwrite/pull/3728)
|
||||
* Update add-oauth2-provider.md in [#4313](https://github.com/appwrite/appwrite/pull/4313)
|
||||
* update readme-cn some doc in [#5278](https://github.com/appwrite/appwrite/pull/5278)
|
||||
* Add accessibility features in [#7042](https://github.com/appwrite/appwrite/pull/7042)
|
||||
* Add Appwrite Cloud to read me. in [#5445](https://github.com/appwrite/appwrite/pull/5445)
|
||||
* Migration throw error in [#9092](https://github.com/appwrite/appwrite/pull/9092)
|
||||
* Fix usage payload bug in [#9097](https://github.com/appwrite/appwrite/pull/9097)
|
||||
* chore: replace occurrences of dbForConsole to dbForPlatform in [#9096](https://github.com/appwrite/appwrite/pull/9096)
|
||||
* fix(realtime): decrement connectionCounter only if connection is known in [#9055](https://github.com/appwrite/appwrite/pull/9055)
|
||||
* payload bug fix in [#9098](https://github.com/appwrite/appwrite/pull/9098)
|
||||
* Fix usage payload bug in [#9099](https://github.com/appwrite/appwrite/pull/9099)
|
||||
* Usage payload debug in [#9101](https://github.com/appwrite/appwrite/pull/9101)
|
||||
* Usage payload debug in [#9103](https://github.com/appwrite/appwrite/pull/9103)
|
||||
* Usage payload debug in [#9104](https://github.com/appwrite/appwrite/pull/9104)
|
||||
* Feat: createFunction abuse labels in [#9102](https://github.com/appwrite/appwrite/pull/9102)
|
||||
* Docs-create-document in [#9105](https://github.com/appwrite/appwrite/pull/9105)
|
||||
* Docs: Create document and unknown attribute error messages. in [#5427](https://github.com/appwrite/appwrite/pull/5427)
|
||||
* Fix: update project accessed at from router and schedulers in [#9109](https://github.com/appwrite/appwrite/pull/9109)
|
||||
* chore: initial commit in [#9111](https://github.com/appwrite/appwrite/pull/9111)
|
||||
* chore: optimise webhooks payload in [#9115](https://github.com/appwrite/appwrite/pull/9115)
|
||||
* Revert "chore: initial commit" in [#9117](https://github.com/appwrite/appwrite/pull/9117)
|
||||
* chore: fix attribute name in [#9118](https://github.com/appwrite/appwrite/pull/9118)
|
||||
* Migrate to redis abuse in [#9124](https://github.com/appwrite/appwrite/pull/9124)
|
||||
* Added webhooks usage stats in [#9125](https://github.com/appwrite/appwrite/pull/9125)
|
||||
* chore remove abuse cleanup in [#9137](https://github.com/appwrite/appwrite/pull/9137)
|
||||
* fix: remove abuse delete trigger in [#9139](https://github.com/appwrite/appwrite/pull/9139)
|
||||
* Remove firebase OAuth API endpoints in [#9144](https://github.com/appwrite/appwrite/pull/9144)
|
||||
* chore: release client sdks in [#9112](https://github.com/appwrite/appwrite/pull/9112)
|
||||
* Update general.php in [#9155](https://github.com/appwrite/appwrite/pull/9155)
|
||||
* feat(swoole): allow configuration override of available cpus in [#9177](https://github.com/appwrite/appwrite/pull/9177)
|
||||
* Usage databases api read writes addition in [#9142](https://github.com/appwrite/appwrite/pull/9142)
|
||||
* Fix dead connections in [#9190](https://github.com/appwrite/appwrite/pull/9190)
|
||||
* Add hostname to audits in [#9165](https://github.com/appwrite/appwrite/pull/9165)
|
||||
* chore: shifted authphone usage tracking to api calls in [#9191](https://github.com/appwrite/appwrite/pull/9191)
|
||||
* Revert "Fix dead connections" in [#9201](https://github.com/appwrite/appwrite/pull/9201)
|
||||
* Add assertEventually to messaging provider logs test in [#9192](https://github.com/appwrite/appwrite/pull/9192)
|
||||
* feat project sms usage in [#9198](https://github.com/appwrite/appwrite/pull/9198)
|
||||
* chore: add audit labels to project resources in [#9056](https://github.com/appwrite/appwrite/pull/9056)
|
||||
* fix sms usage in [#9207](https://github.com/appwrite/appwrite/pull/9207)
|
||||
* Update database in [#9202](https://github.com/appwrite/appwrite/pull/9202)
|
||||
* Fix dead connections in [#9213](https://github.com/appwrite/appwrite/pull/9213)
|
||||
* Revert "Fix dead connections" in [#9214](https://github.com/appwrite/appwrite/pull/9214)
|
||||
* Add logs db init for consistency in [#9163](https://github.com/appwrite/appwrite/pull/9163)
|
||||
* Split the collection definitions in [#9153](https://github.com/appwrite/appwrite/pull/9153)
|
||||
* Log path with populated parameters in [#9220](https://github.com/appwrite/appwrite/pull/9220)
|
||||
* Add missing scope on function template in [#9208](https://github.com/appwrite/appwrite/pull/9208)
|
||||
* Add relatedCollection default in [#9225](https://github.com/appwrite/appwrite/pull/9225)
|
||||
* fix: function usage in [#9235](https://github.com/appwrite/appwrite/pull/9235)
|
||||
* feat: optimise events payloads in [#9232](https://github.com/appwrite/appwrite/pull/9232)
|
||||
* Optimise webhook events in [#9168](https://github.com/appwrite/appwrite/pull/9168)
|
||||
* fix: maintenance job missing type in [#9238](https://github.com/appwrite/appwrite/pull/9238)
|
||||
* Update Fetch to 0.3.0 in [#9245](https://github.com/appwrite/appwrite/pull/9245)
|
||||
* Fix maintenance job in [#9247](https://github.com/appwrite/appwrite/pull/9247)
|
||||
* chore: add missing case for executions in [#9248](https://github.com/appwrite/appwrite/pull/9248)
|
||||
* Add index dependency exception in [#9226](https://github.com/appwrite/appwrite/pull/9226)
|
||||
* chore: fix benchmarking test when made from fork in [#9233](https://github.com/appwrite/appwrite/pull/9233)
|
||||
* Update SDK Generator versions in [#9188](https://github.com/appwrite/appwrite/pull/9188)
|
||||
* chore: skipped job instead of throwing error in [#9250](https://github.com/appwrite/appwrite/pull/9250)
|
||||
* Implement new SDK Class on 1.6.x in [#9237](https://github.com/appwrite/appwrite/pull/9237)
|
||||
* Delete collection before Appwrite's attributes in [#9256](https://github.com/appwrite/appwrite/pull/9256)
|
||||
* Feat batch usage dump in [#9255](https://github.com/appwrite/appwrite/pull/9255)
|
||||
* Fix cloud tests in [#9261](https://github.com/appwrite/appwrite/pull/9261)
|
||||
* Usage: Databases reads writes in [#9260](https://github.com/appwrite/appwrite/pull/9260)
|
||||
* Update: Latest sdk specs in [#9274](https://github.com/appwrite/appwrite/pull/9274)
|
||||
* Revert "Feat batch usage dump" in [#9276](https://github.com/appwrite/appwrite/pull/9276)
|
||||
* feat: add fast2SMS adapter in [#9263](https://github.com/appwrite/appwrite/pull/9263)
|
||||
* Update Sdk Generator dependency in [#9280](https://github.com/appwrite/appwrite/pull/9280)
|
||||
* Transformed at addition in [#9281](https://github.com/appwrite/appwrite/pull/9281)
|
||||
* Docs: clarify update endpoints only work on draft messages in [#9236](https://github.com/appwrite/appwrite/pull/9236)
|
||||
* Update sdk generator dependency in [#9282](https://github.com/appwrite/appwrite/pull/9282)
|
||||
* Revert "Transformed at addition" in [#9284](https://github.com/appwrite/appwrite/pull/9284)
|
||||
* replaced init for cloud link in [#9285](https://github.com/appwrite/appwrite/pull/9285)
|
||||
* Add transformed at in [#9289](https://github.com/appwrite/appwrite/pull/9289)
|
||||
* Make migrations use Dynamic keys for destination in [#9291](https://github.com/appwrite/appwrite/pull/9291)
|
||||
* Make sessions limit tests assert eventually in [#9298](https://github.com/appwrite/appwrite/pull/9298)
|
||||
* Chore update database in [#9306](https://github.com/appwrite/appwrite/pull/9306)
|
||||
* feat: add AMQP queues in [#9287](https://github.com/appwrite/appwrite/pull/9287)
|
||||
* fix(test): use assertEventually instead of while(true) in [#9308](https://github.com/appwrite/appwrite/pull/9308)
|
||||
* fix(certificate worker): events are published without queue name in [#9309](https://github.com/appwrite/appwrite/pull/9309)
|
||||
* chore: update utopia-php/queue to 0.8.1 in [#9311](https://github.com/appwrite/appwrite/pull/9311)
|
||||
* chore: update utopia-php/queue to 0.8.2 in [#9312](https://github.com/appwrite/appwrite/pull/9312)
|
||||
* fix(schedule-tasks): revert back to direct pool usage in [#9313](https://github.com/appwrite/appwrite/pull/9313)
|
||||
* feat: custom app schemes in [#9262](https://github.com/appwrite/appwrite/pull/9262)
|
||||
* Revert "feat: custom app schemes" in [#9319](https://github.com/appwrite/appwrite/pull/9319)
|
||||
* Restore "feat: custom app schemes"" in [#9320](https://github.com/appwrite/appwrite/pull/9320)
|
||||
* Revert "Restore "feat: custom app schemes""" in [#9323](https://github.com/appwrite/appwrite/pull/9323)
|
||||
* chore: update dependencies in [#9330](https://github.com/appwrite/appwrite/pull/9330)
|
||||
* Feat: logs DB in [#9272](https://github.com/appwrite/appwrite/pull/9272)
|
||||
* Catch invalid index in [#9329](https://github.com/appwrite/appwrite/pull/9329)
|
||||
* Fix: missing call for image transformations counting in [#9342](https://github.com/appwrite/appwrite/pull/9342)
|
||||
* Fix drop abuse on shared table project delete in [#9346](https://github.com/appwrite/appwrite/pull/9346)
|
||||
* Only run all table mode tests on db update in [#9338](https://github.com/appwrite/appwrite/pull/9338)
|
||||
* Fix: missing periodic metric in [#9350](https://github.com/appwrite/appwrite/pull/9350)
|
||||
* feat(builds): check if function is blocked before building in [#9332](https://github.com/appwrite/appwrite/pull/9332)
|
||||
* feat: batch create audit logs in [#9347](https://github.com/appwrite/appwrite/pull/9347)
|
||||
* Chore: Update migrations in [#9355](https://github.com/appwrite/appwrite/pull/9355)
|
||||
* Fix: metric time was not being written to DB in [#9354](https://github.com/appwrite/appwrite/pull/9354)
|
||||
* Fix patch index validation in [#9356](https://github.com/appwrite/appwrite/pull/9356)
|
||||
* Fix image trnasformation metrics in [#9370](https://github.com/appwrite/appwrite/pull/9370)
|
||||
* Use batch delete in worker in [#9375](https://github.com/appwrite/appwrite/pull/9375)
|
||||
* Fix Model Platform is missing response key: store in [#9361](https://github.com/appwrite/appwrite/pull/9361)
|
||||
* Feat key segmented usage in [#9336](https://github.com/appwrite/appwrite/pull/9336)
|
||||
* Feat messaging metrics in [#9353](https://github.com/appwrite/appwrite/pull/9353)
|
||||
* Fix removed audits for shared v2 in [#9388](https://github.com/appwrite/appwrite/pull/9388)
|
||||
* chore: bump utopia-php/image to 0.8.0 in [#9390](https://github.com/appwrite/appwrite/pull/9390)
|
||||
* Fix outdated CLI commands in documentation in [#9122](https://github.com/appwrite/appwrite/pull/9122)
|
||||
* disable logs display in [#9398](https://github.com/appwrite/appwrite/pull/9398)
|
||||
* Log batches per project in [#9403](https://github.com/appwrite/appwrite/pull/9403)
|
||||
* Batch per project in [#9410](https://github.com/appwrite/appwrite/pull/9410)
|
||||
* Fix: stats resources only queue projects accessed in last 3 hours in [#9411](https://github.com/appwrite/appwrite/pull/9411)
|
||||
* Track options requests in [#9397](https://github.com/appwrite/appwrite/pull/9397)
|
||||
* chore: bump docker-base in [#9406](https://github.com/appwrite/appwrite/pull/9406)
|
||||
* refactor: migrate Realtime::send calls to queueForRealtime in [#9325](https://github.com/appwrite/appwrite/pull/9325)
|
||||
* Revert "Fix: stats resources only queue projects accessed in last 3 hours" in [#9424](https://github.com/appwrite/appwrite/pull/9424)
|
||||
* Remove usage and usage dump in favor of stats-usage and stats-usage-dump in [#9339](https://github.com/appwrite/appwrite/pull/9339)
|
||||
* Fix: disable dual writing in [#9429](https://github.com/appwrite/appwrite/pull/9429)
|
||||
* Disable transformedAt update for console users in [#9425](https://github.com/appwrite/appwrite/pull/9425)
|
||||
* chore: add image transformation stats to usage endpoint in [#9393](https://github.com/appwrite/appwrite/pull/9393)
|
||||
* chore: added timeout to deployment builds in tests in [#9426](https://github.com/appwrite/appwrite/pull/9426)
|
||||
* fix: model for image transformations in usage project in [#9442](https://github.com/appwrite/appwrite/pull/9442)
|
||||
* Feat: calculate database storage in stats-resources in [#9443](https://github.com/appwrite/appwrite/pull/9443)
|
||||
* Activities batch writes in [#9438](https://github.com/appwrite/appwrite/pull/9438)
|
||||
* chore: bump cache 0.12.x in [#9412](https://github.com/appwrite/appwrite/pull/9412)
|
||||
* chore: queue console project for maintenance delete in [#9479](https://github.com/appwrite/appwrite/pull/9479)
|
||||
* chore: added logsdb for deletes worker in [#9462](https://github.com/appwrite/appwrite/pull/9462)
|
||||
* Feat: calculate and log time taken for each project in [#9491](https://github.com/appwrite/appwrite/pull/9491)
|
||||
* chore: update initializing dbForLogs in [#9494](https://github.com/appwrite/appwrite/pull/9494)
|
||||
* Feat bulk audit delete in [#9487](https://github.com/appwrite/appwrite/pull/9487)
|
||||
* Prepare 1.6.2 release in [#9499](https://github.com/appwrite/appwrite/pull/9499)
|
||||
* Regenerate specs in [#9497](https://github.com/appwrite/appwrite/pull/9497)
|
||||
* Regenerate examples in [#9498](https://github.com/appwrite/appwrite/pull/9498)
|
||||
* chore: bump sdk in [#9414](https://github.com/appwrite/appwrite/pull/9414)
|
||||
* update queue to 0.9.* in [#9505](https://github.com/appwrite/appwrite/pull/9505)
|
||||
* Feat improve delete queries in [#9507](https://github.com/appwrite/appwrite/pull/9507)
|
||||
* Feat: Add rule attributes in [#9508](https://github.com/appwrite/appwrite/pull/9508)
|
||||
* Sync main into 1.6.x in [#9496](https://github.com/appwrite/appwrite/pull/9496)
|
||||
* Bump console to version 5.2.53 in [#9495](https://github.com/appwrite/appwrite/pull/9495)
|
||||
* Prepare 1.6.1 release in [#9294](https://github.com/appwrite/appwrite/pull/9294)
|
||||
* Improve delete ordering in [#9512](https://github.com/appwrite/appwrite/pull/9512)
|
||||
* Cleanups in [#9511](https://github.com/appwrite/appwrite/pull/9511)
|
||||
* Feat dynamic regions in [#9408](https://github.com/appwrite/appwrite/pull/9408)
|
||||
* Feat env vars to system lib in [#9515](https://github.com/appwrite/appwrite/pull/9515)
|
||||
* Feat: domains count in [#9514](https://github.com/appwrite/appwrite/pull/9514)
|
||||
* Migration read from db in [#9529](https://github.com/appwrite/appwrite/pull/9529)
|
||||
* feat: add pool telemetry in [#9530](https://github.com/appwrite/appwrite/pull/9530)
|
||||
* Disable PDO persistence since we manage our own pool in [#9526](https://github.com/appwrite/appwrite/pull/9526)
|
||||
* chore: set min operations to 1 for reads and writes in [#9536](https://github.com/appwrite/appwrite/pull/9536)
|
||||
* Remove default region in [#9430](https://github.com/appwrite/appwrite/pull/9430)
|
||||
* Use cursor pagination with bigger limit for maintenance project loop in [#9546](https://github.com/appwrite/appwrite/pull/9546)
|
||||
* chore: stop tests on failure in [#9525](https://github.com/appwrite/appwrite/pull/9525)
|
||||
* chore: only update total count for privileged users in [#9554](https://github.com/appwrite/appwrite/pull/9554)
|
||||
* refactor: initialization of audit retention in [#9563](https://github.com/appwrite/appwrite/pull/9563)
|
||||
* Delete worker queries fixes in [#9523](https://github.com/appwrite/appwrite/pull/9523)
|
||||
* Bump database 0.62.x in [#9568](https://github.com/appwrite/appwrite/pull/9568)
|
||||
* Fix: schedules region filtering in [#9577](https://github.com/appwrite/appwrite/pull/9577)
|
||||
* Deletes worker fix selects for pagination in [#9578](https://github.com/appwrite/appwrite/pull/9578)
|
||||
* Add $permissions for delete documents selects in [#9579](https://github.com/appwrite/appwrite/pull/9579)
|
||||
* chore(audits): return queue pre-fetch results in [#9533](https://github.com/appwrite/appwrite/pull/9533)
|
||||
* Revert "chore(audits): return queue pre-fetch results" in [#9586](https://github.com/appwrite/appwrite/pull/9586)
|
||||
* Feat multi tenant insert in [#9573](https://github.com/appwrite/appwrite/pull/9573)
|
||||
* Add order by for cursor in [#9588](https://github.com/appwrite/appwrite/pull/9588)
|
||||
* Feat update fetch in [#9592](https://github.com/appwrite/appwrite/pull/9592)
|
||||
* Fix tenant casting in [#9598](https://github.com/appwrite/appwrite/pull/9598)
|
||||
* Feat update ws in [#9602](https://github.com/appwrite/appwrite/pull/9602)
|
||||
* Update database in [#9603](https://github.com/appwrite/appwrite/pull/9603)
|
||||
* Fix: image transformation cache in [#9608](https://github.com/appwrite/appwrite/pull/9608)
|
||||
* Remove audit payload in [#9610](https://github.com/appwrite/appwrite/pull/9610)
|
||||
* Sample rate from DSN in [#9559](https://github.com/appwrite/appwrite/pull/9559)
|
||||
* Restrict role change for sole org owner in [#9615](https://github.com/appwrite/appwrite/pull/9615)
|
||||
* chore: update php image to 0.8.1 in [#9616](https://github.com/appwrite/appwrite/pull/9616)
|
||||
* feat: refactor executor setup in [#9420](https://github.com/appwrite/appwrite/pull/9420)
|
||||
* chore: update gitpod.yml config in [#9561](https://github.com/appwrite/appwrite/pull/9561)
|
||||
* chore: update dependencies in [#9625](https://github.com/appwrite/appwrite/pull/9625)
|
||||
* Update migrations lib in [#9628](https://github.com/appwrite/appwrite/pull/9628)
|
||||
* feat: cache telemetry in [#9624](https://github.com/appwrite/appwrite/pull/9624)
|
||||
* Bump console to version 5.2.56 in [#9631](https://github.com/appwrite/appwrite/pull/9631)
|
||||
* Multi region support in [#8667](https://github.com/appwrite/appwrite/pull/8667)
|
||||
* Revert "Multi region support" in [#9632](https://github.com/appwrite/appwrite/pull/9632)
|
||||
* Revert "Revert "Multi region support"" in [#9636](https://github.com/appwrite/appwrite/pull/9636)
|
||||
* Fix tasks in [#9644](https://github.com/appwrite/appwrite/pull/9644)
|
||||
* chore: updated the migration version to 8.6 in [#9646](https://github.com/appwrite/appwrite/pull/9646)
|
||||
* Fix: merge the working of StatsUsage and StatsUsageDump in [#9585](https://github.com/appwrite/appwrite/pull/9585)
|
||||
* Update database in [#9643](https://github.com/appwrite/appwrite/pull/9643)
|
||||
* chore: fix error logging for CLI tasks in [#9651](https://github.com/appwrite/appwrite/pull/9651)
|
||||
* fix: usage test assertion in [#9653](https://github.com/appwrite/appwrite/pull/9653)
|
||||
* Fix keys in [#9656](https://github.com/appwrite/appwrite/pull/9656)
|
||||
* Feat: multi tenant dual writing in [#9583](https://github.com/appwrite/appwrite/pull/9583)
|
||||
* Fix/throwing 400 for null order attributes in [#9657](https://github.com/appwrite/appwrite/pull/9657)
|
||||
* feat: sdk group attribute in [#9596](https://github.com/appwrite/appwrite/pull/9596)
|
||||
* Add configurable function and build size in [#9648](https://github.com/appwrite/appwrite/pull/9648)
|
||||
* feat: update API endpoint in the code examples in [#8933](https://github.com/appwrite/appwrite/pull/8933)
|
||||
* chore: abstract token secret hiding to response model in [#9574](https://github.com/appwrite/appwrite/pull/9574)
|
||||
* chore: update sdks in [#9655](https://github.com/appwrite/appwrite/pull/9655)
|
||||
* feat: allow non-critical events to ignore exceptions when enqueuing the event in [#9680](https://github.com/appwrite/appwrite/pull/9680)
|
||||
* Revert "Add configurable function and build size" in [#9681](https://github.com/appwrite/appwrite/pull/9681)
|
||||
* core: introduce endpoint.docs in specs in [#9685](https://github.com/appwrite/appwrite/pull/9685)
|
||||
* fix: remove content-type header from get request specs in [#9666](https://github.com/appwrite/appwrite/pull/9666)
|
||||
* chore: update flutter sdk in [#9691](https://github.com/appwrite/appwrite/pull/9691)
|
||||
|
||||
# Version 1.6.1
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<?php
|
||||
|
||||
use Appwrite\Auth\Auth;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Helpers\ID;
|
||||
|
||||
@@ -172,7 +173,7 @@ return [
|
||||
'size' => 256,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => 'argon2',
|
||||
'default' => Auth::DEFAULT_ALGO,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
@@ -183,7 +184,7 @@ return [
|
||||
'size' => 65535,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => ['type' => 'argon2', 'memoryCost' => 2048, 'timeCost' => 4, 'threads' => 3],
|
||||
'default' => Auth::DEFAULT_ALGO_OPTIONS,
|
||||
'array' => false,
|
||||
'filters' => ['json'],
|
||||
],
|
||||
@@ -1114,9 +1115,9 @@ return [
|
||||
[
|
||||
'$id' => ID::custom('expire'),
|
||||
'type' => Database::VAR_DATETIME,
|
||||
'format' => '',
|
||||
'size' => 0,
|
||||
'required' => false,
|
||||
'format' => '',
|
||||
'signed' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
* Initializes console project document.
|
||||
*/
|
||||
|
||||
use Appwrite\Auth\Auth;
|
||||
use Appwrite\Network\Platform;
|
||||
use Utopia\Database\Helpers\ID;
|
||||
use Utopia\System\System;
|
||||
@@ -37,7 +38,7 @@ $console = [
|
||||
'mockNumbers' => [],
|
||||
'invites' => System::getEnv('_APP_CONSOLE_INVITES', 'enabled') === 'enabled',
|
||||
'limit' => (System::getEnv('_APP_CONSOLE_WHITELIST_ROOT', 'enabled') === 'enabled') ? 1 : 0, // limit signup to 1 user
|
||||
'duration' => TOKEN_EXPIRATION_LOGIN_LONG, // 1 Year in seconds
|
||||
'duration' => Auth::TOKEN_EXPIRATION_LOGIN_LONG, // 1 Year in seconds
|
||||
'sessionAlerts' => System::getEnv('_APP_CONSOLE_SESSION_ALERTS', 'disabled') === 'enabled',
|
||||
'invalidateSessions' => true
|
||||
],
|
||||
|
||||
@@ -79,7 +79,7 @@ return [
|
||||
[
|
||||
'key' => 'apple',
|
||||
'name' => 'Apple',
|
||||
'version' => '13.2.2',
|
||||
'version' => '13.3.0',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-apple',
|
||||
'package' => 'https://github.com/appwrite/sdk-for-apple',
|
||||
'enabled' => true,
|
||||
@@ -226,7 +226,7 @@ return [
|
||||
[
|
||||
'key' => 'cli',
|
||||
'name' => 'Command Line',
|
||||
'version' => '10.2.1',
|
||||
'version' => '10.2.2',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-cli',
|
||||
'package' => 'https://www.npmjs.com/package/appwrite-cli',
|
||||
'enabled' => true,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<?php
|
||||
|
||||
use Appwrite\Auth\Auth;
|
||||
|
||||
$member = [
|
||||
'global',
|
||||
@@ -91,7 +92,7 @@ $admins = [
|
||||
];
|
||||
|
||||
return [
|
||||
USER_ROLE_GUESTS => [
|
||||
Auth::USER_ROLE_GUESTS => [
|
||||
'label' => 'Guests',
|
||||
'scopes' => [
|
||||
'global',
|
||||
@@ -111,23 +112,23 @@ return [
|
||||
'execution.write',
|
||||
],
|
||||
],
|
||||
USER_ROLE_USERS => [
|
||||
Auth::USER_ROLE_USERS => [
|
||||
'label' => 'Users',
|
||||
'scopes' => \array_merge($member),
|
||||
],
|
||||
USER_ROLE_ADMIN => [
|
||||
Auth::USER_ROLE_ADMIN => [
|
||||
'label' => 'Admin',
|
||||
'scopes' => \array_merge($admins),
|
||||
],
|
||||
USER_ROLE_DEVELOPER => [
|
||||
Auth::USER_ROLE_DEVELOPER => [
|
||||
'label' => 'Developer',
|
||||
'scopes' => \array_merge($admins),
|
||||
],
|
||||
USER_ROLE_OWNER => [
|
||||
Auth::USER_ROLE_OWNER => [
|
||||
'label' => 'Owner',
|
||||
'scopes' => \array_merge($member, $admins),
|
||||
],
|
||||
USER_ROLE_APPS => [
|
||||
Auth::USER_ROLE_APPS => [
|
||||
'label' => 'Applications',
|
||||
'scopes' => ['global', 'health.read', 'graphql'],
|
||||
],
|
||||
|
||||
+174
-267
File diff suppressed because it is too large
Load Diff
@@ -74,6 +74,7 @@ App::get('/v1/console/variables')
|
||||
// Combine CAA domain with most common flags and tag (no parameters)
|
||||
'_APP_DOMAIN_TARGET_CAA' => '0 issue "' . System::getEnv('_APP_DOMAIN_TARGET_CAA') . '"',
|
||||
'_APP_STORAGE_LIMIT' => +System::getEnv('_APP_STORAGE_LIMIT'),
|
||||
'_APP_COMPUTE_BUILD_TIMEOUT' => +System::getEnv('_APP_COMPUTE_BUILD_TIMEOUT'),
|
||||
'_APP_COMPUTE_SIZE_LIMIT' => +System::getEnv('_APP_COMPUTE_SIZE_LIMIT'),
|
||||
'_APP_USAGE_STATS' => System::getEnv('_APP_USAGE_STATS'),
|
||||
'_APP_VCS_ENABLED' => $isVcsEnabled,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<?php
|
||||
|
||||
use Ahc\Jwt\JWT;
|
||||
use Appwrite\Auth\Auth;
|
||||
use Appwrite\Auth\Validator\MockNumber;
|
||||
use Appwrite\Event\Delete;
|
||||
use Appwrite\Event\Mail;
|
||||
@@ -117,7 +118,7 @@ App::post('/v1/projects')
|
||||
'maxSessions' => APP_LIMIT_USER_SESSIONS_DEFAULT,
|
||||
'passwordHistory' => 0,
|
||||
'passwordDictionary' => false,
|
||||
'duration' => TOKEN_EXPIRATION_LOGIN_LONG,
|
||||
'duration' => Auth::TOKEN_EXPIRATION_LOGIN_LONG,
|
||||
'personalDataCheck' => false,
|
||||
'mockNumbers' => [],
|
||||
'sessionAlerts' => false,
|
||||
|
||||
@@ -28,9 +28,6 @@ use MaxMind\Db\Reader;
|
||||
use Utopia\Abuse\Abuse;
|
||||
use Utopia\App;
|
||||
use Utopia\Audit\Audit;
|
||||
use Utopia\Auth\Proofs\Password;
|
||||
use Utopia\Auth\Proofs\Token;
|
||||
use Utopia\Auth\Store;
|
||||
use Utopia\Config\Config;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\DateTime;
|
||||
@@ -474,9 +471,10 @@ App::post('/v1/teams/:teamId/memberships')
|
||||
->param('phone', '', new Phone(), 'Phone number. Format this number with a leading \'+\' and a country code, e.g., +16175551212.', true)
|
||||
->param('roles', [], function (Document $project) {
|
||||
if ($project->getId() === 'console') {
|
||||
;
|
||||
$roles = array_keys(Config::getParam('roles', []));
|
||||
$roles = array_filter($roles, function ($role) {
|
||||
return !in_array($role, [USER_ROLE_APPS, USER_ROLE_GUESTS, USER_ROLE_USERS]);
|
||||
array_filter($roles, function ($role) {
|
||||
return !in_array($role, [Auth::USER_ROLE_APPS, Auth::USER_ROLE_GUESTS, Auth::USER_ROLE_USERS]);
|
||||
});
|
||||
return new ArrayList(new WhiteList($roles), APP_LIMIT_ARRAY_PARAMS_SIZE);
|
||||
}
|
||||
@@ -495,9 +493,7 @@ App::post('/v1/teams/:teamId/memberships')
|
||||
->inject('timelimit')
|
||||
->inject('queueForStatsUsage')
|
||||
->inject('plan')
|
||||
->inject('proofForPassword')
|
||||
->inject('proofForToken')
|
||||
->action(function (string $teamId, string $email, string $userId, string $phone, array $roles, string $url, string $name, Response $response, Document $project, Document $user, Database $dbForProject, Locale $locale, Mail $queueForMails, Messaging $queueForMessaging, Event $queueForEvents, callable $timelimit, StatsUsage $queueForStatsUsage, array $plan, Password $proofForPassword, Token $proofForToken) {
|
||||
->action(function (string $teamId, string $email, string $userId, string $phone, array $roles, string $url, string $name, Response $response, Document $project, Document $user, Database $dbForProject, Locale $locale, Mail $queueForMails, Messaging $queueForMessaging, Event $queueForEvents, callable $timelimit, StatsUsage $queueForStatsUsage, array $plan) {
|
||||
$isAppUser = Auth::isAppUser(Authorization::getRoles());
|
||||
$isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles());
|
||||
|
||||
@@ -570,7 +566,6 @@ App::post('/v1/teams/:teamId/memberships')
|
||||
|
||||
try {
|
||||
$userId = ID::unique();
|
||||
$hash = $proofForPassword->hash($proofForPassword->generate());
|
||||
$invitee = Authorization::skip(fn () => $dbForProject->createDocument('users', new Document([
|
||||
'$id' => $userId,
|
||||
'$permissions' => [
|
||||
@@ -584,9 +579,9 @@ App::post('/v1/teams/:teamId/memberships')
|
||||
'emailVerification' => false,
|
||||
'status' => true,
|
||||
// TODO: Set password empty?
|
||||
'password' => $hash,
|
||||
'hash' => $proofForPassword->getHash()->getName(),
|
||||
'hashOptions' => $proofForPassword->getHash()->getOptions(),
|
||||
'password' => Auth::passwordHash(Auth::passwordGenerator(), Auth::DEFAULT_ALGO, Auth::DEFAULT_ALGO_OPTIONS),
|
||||
'hash' => Auth::DEFAULT_ALGO,
|
||||
'hashOptions' => Auth::DEFAULT_ALGO_OPTIONS,
|
||||
/**
|
||||
* Set the password update time to 0 for users created using
|
||||
* team invite and OAuth to allow password updates without an
|
||||
@@ -618,7 +613,7 @@ App::post('/v1/teams/:teamId/memberships')
|
||||
Query::equal('teamInternalId', [$team->getSequence()]),
|
||||
]);
|
||||
|
||||
$secret = $proofForToken->generate();
|
||||
$secret = Auth::tokenGenerator();
|
||||
if ($membership->isEmpty()) {
|
||||
$membershipId = ID::unique();
|
||||
$membership = new Document([
|
||||
@@ -638,7 +633,7 @@ App::post('/v1/teams/:teamId/memberships')
|
||||
'invited' => DateTime::now(),
|
||||
'joined' => ($isPrivilegedUser || $isAppUser) ? DateTime::now() : null,
|
||||
'confirm' => ($isPrivilegedUser || $isAppUser),
|
||||
'secret' => $proofForToken->hash($secret),
|
||||
'secret' => Auth::hash($secret),
|
||||
'search' => implode(' ', [$membershipId, $invitee->getId()])
|
||||
]);
|
||||
|
||||
@@ -651,7 +646,7 @@ App::post('/v1/teams/:teamId/memberships')
|
||||
}
|
||||
|
||||
} elseif ($membership->getAttribute('confirm') === false) {
|
||||
$membership->setAttribute('secret', $proofForToken->hash($secret));
|
||||
$membership->setAttribute('secret', Auth::hash($secret));
|
||||
$membership->setAttribute('invited', DateTime::now());
|
||||
|
||||
if ($isPrivilegedUser || $isAppUser) {
|
||||
@@ -1075,7 +1070,7 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId')
|
||||
if ($project->getId() === 'console') {
|
||||
$roles = array_keys(Config::getParam('roles', []));
|
||||
array_filter($roles, function ($role) {
|
||||
return !in_array($role, [USER_ROLE_APPS, USER_ROLE_GUESTS, USER_ROLE_USERS]);
|
||||
return !in_array($role, [Auth::USER_ROLE_APPS, Auth::USER_ROLE_GUESTS, Auth::USER_ROLE_USERS]);
|
||||
});
|
||||
return new ArrayList(new WhiteList($roles), APP_LIMIT_ARRAY_PARAMS_SIZE);
|
||||
}
|
||||
@@ -1190,9 +1185,7 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId/status')
|
||||
->inject('project')
|
||||
->inject('geodb')
|
||||
->inject('queueForEvents')
|
||||
->inject('store')
|
||||
->inject('proofForToken')
|
||||
->action(function (string $teamId, string $membershipId, string $userId, string $secret, Request $request, Response $response, Document $user, Database $dbForProject, Document $project, Reader $geodb, Event $queueForEvents, Store $store, Token $proofForToken) {
|
||||
->action(function (string $teamId, string $membershipId, string $userId, string $secret, Request $request, Response $response, Document $user, Database $dbForProject, Document $project, Reader $geodb, Event $queueForEvents) {
|
||||
$protocol = $request->getProtocol();
|
||||
|
||||
$membership = $dbForProject->getDocument('memberships', $membershipId);
|
||||
@@ -1211,7 +1204,7 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId/status')
|
||||
throw new Exception(Exception::TEAM_MEMBERSHIP_MISMATCH);
|
||||
}
|
||||
|
||||
if (!$proofForToken->verify($secret, $membership->getAttribute('secret'))) {
|
||||
if (Auth::hash($secret) !== $membership->getAttribute('secret')) {
|
||||
throw new Exception(Exception::TEAM_INVALID_SECRET);
|
||||
}
|
||||
|
||||
@@ -1245,9 +1238,9 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId/status')
|
||||
|
||||
$detector = new Detector($request->getUserAgent('UNKNOWN'));
|
||||
$record = $geodb->get($request->getIP());
|
||||
$authDuration = $project->getAttribute('auths', [])['duration'] ?? TOKEN_EXPIRATION_LOGIN_LONG;
|
||||
$authDuration = $project->getAttribute('auths', [])['duration'] ?? Auth::TOKEN_EXPIRATION_LOGIN_LONG;
|
||||
$expire = DateTime::addSeconds(new \DateTime(), $authDuration);
|
||||
$secret = $proofForToken->generate();
|
||||
$secret = Auth::tokenGenerator();
|
||||
$session = new Document(array_merge([
|
||||
'$id' => ID::unique(),
|
||||
'$permissions' => [
|
||||
@@ -1257,9 +1250,9 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId/status')
|
||||
],
|
||||
'userId' => $user->getId(),
|
||||
'userInternalId' => $user->getSequence(),
|
||||
'provider' => SESSION_PROVIDER_EMAIL,
|
||||
'provider' => Auth::SESSION_PROVIDER_EMAIL,
|
||||
'providerUid' => $user->getAttribute('email'),
|
||||
'secret' => $proofForToken->hash($secret), // One way hash encryption to protect DB leak
|
||||
'secret' => Auth::hash($secret), // One way hash encryption to protect DB leak
|
||||
'userAgent' => $request->getUserAgent('UNKNOWN'),
|
||||
'ip' => $request->getIP(),
|
||||
'factors' => ['email'],
|
||||
@@ -1271,19 +1264,14 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId/status')
|
||||
|
||||
Authorization::setRole(Role::user($userId)->toString());
|
||||
|
||||
$encoded = $store
|
||||
->setProperty('id', $user->getId())
|
||||
->setProperty('secret', $secret)
|
||||
->encode();
|
||||
|
||||
if (!Config::getParam('domainVerification')) {
|
||||
$response->addHeader('X-Fallback-Cookies', \json_encode([$store->getKey() => $encoded]));
|
||||
$response->addHeader('X-Fallback-Cookies', \json_encode([Auth::$cookieName => Auth::encodeSession($user->getId(), $secret)]));
|
||||
}
|
||||
|
||||
$response
|
||||
->addCookie(
|
||||
name: $store->getKey() . '_legacy',
|
||||
value: $encoded,
|
||||
name: Auth::$cookieName . '_legacy',
|
||||
value: Auth::encodeSession($user->getId(), $secret),
|
||||
expire: (new \DateTime($expire))->getTimestamp(),
|
||||
path: '/',
|
||||
domain: Config::getParam('cookieDomain'),
|
||||
@@ -1291,8 +1279,8 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId/status')
|
||||
httponly: true
|
||||
)
|
||||
->addCookie(
|
||||
name: $store->getKey(),
|
||||
value: $encoded,
|
||||
name: Auth::$cookieName,
|
||||
value: Auth::encodeSession($user->getId(), $secret),
|
||||
expire: (new \DateTime($expire))->getTimestamp(),
|
||||
path: '/',
|
||||
domain: Config::getParam('cookieDomain'),
|
||||
|
||||
+43
-101
@@ -1,6 +1,7 @@
|
||||
<?php
|
||||
|
||||
use Ahc\Jwt\JWT;
|
||||
use Appwrite\Auth\Auth;
|
||||
use Appwrite\Auth\MFA\Type;
|
||||
use Appwrite\Auth\MFA\Type\TOTP;
|
||||
use Appwrite\Auth\Validator\Password;
|
||||
@@ -31,18 +32,6 @@ use Appwrite\Utopia\Response;
|
||||
use MaxMind\Db\Reader;
|
||||
use Utopia\App;
|
||||
use Utopia\Audit\Audit;
|
||||
use Utopia\Auth\Hash;
|
||||
use Utopia\Auth\Hashes\Argon2;
|
||||
use Utopia\Auth\Hashes\Bcrypt;
|
||||
use Utopia\Auth\Hashes\MD5;
|
||||
use Utopia\Auth\Hashes\PHPass;
|
||||
use Utopia\Auth\Hashes\Plaintext;
|
||||
use Utopia\Auth\Hashes\Scrypt;
|
||||
use Utopia\Auth\Hashes\ScryptModified;
|
||||
use Utopia\Auth\Hashes\Sha;
|
||||
use Utopia\Auth\Proofs\Password as ProofsPassword;
|
||||
use Utopia\Auth\Proofs\Token;
|
||||
use Utopia\Auth\Store;
|
||||
use Utopia\Config\Config;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\DateTime;
|
||||
@@ -71,9 +60,10 @@ use Utopia\Validator\Text;
|
||||
use Utopia\Validator\WhiteList;
|
||||
|
||||
/** TODO: Remove function when we move to using utopia/platform */
|
||||
function createUser(Hash $hash, string $userId, ?string $email, ?string $password, ?string $phone, string $name, Document $project, Database $dbForProject, Hooks $hooks): Document
|
||||
function createUser(string $hash, mixed $hashOptions, string $userId, ?string $email, ?string $password, ?string $phone, string $name, Document $project, Database $dbForProject, Hooks $hooks): Document
|
||||
{
|
||||
$plaintextPassword = $password;
|
||||
$hashOptionsObject = (\is_string($hashOptions)) ? \json_decode($hashOptions, true) : $hashOptions; // Cast to JSON array
|
||||
$passwordHistory = $project->getAttribute('auths', [])['passwordHistory'] ?? 0;
|
||||
|
||||
if (!empty($email)) {
|
||||
@@ -107,18 +97,7 @@ function createUser(Hash $hash, string $userId, ?string $email, ?string $passwor
|
||||
}
|
||||
}
|
||||
|
||||
$hashedPassword = null;
|
||||
|
||||
if (!empty($password)) {
|
||||
if ($hash instanceof Plaintext) { // Password was never hashed, hash it with the default hash
|
||||
$defaultHash = new ProofsPassword();
|
||||
$hashedPassword = $defaultHash->hash($password);
|
||||
$hash = $defaultHash->getHash();
|
||||
} else {
|
||||
$hashedPassword = $password;
|
||||
}
|
||||
}
|
||||
|
||||
$password = (!empty($password)) ? ($hash === 'plaintext' ? Auth::passwordHash($password, $hash, $hashOptionsObject) : $password) : null;
|
||||
$user = new Document([
|
||||
'$id' => $userId,
|
||||
'$permissions' => [
|
||||
@@ -132,11 +111,11 @@ function createUser(Hash $hash, string $userId, ?string $email, ?string $passwor
|
||||
'phoneVerification' => false,
|
||||
'status' => true,
|
||||
'labels' => [],
|
||||
'password' => $hashedPassword,
|
||||
'passwordHistory' => is_null($hashedPassword) || $passwordHistory === 0 ? [] : [$hashedPassword],
|
||||
'passwordUpdate' => (!empty($hashedPassword)) ? DateTime::now() : null,
|
||||
'hash' => $hash->getName(),
|
||||
'hashOptions' => $hash->getOptions(),
|
||||
'password' => $password,
|
||||
'passwordHistory' => is_null($password) || $passwordHistory === 0 ? [] : [$password],
|
||||
'passwordUpdate' => (!empty($password)) ? DateTime::now() : null,
|
||||
'hash' => $hash === 'plaintext' ? Auth::DEFAULT_ALGO : $hash,
|
||||
'hashOptions' => $hash === 'plaintext' ? Auth::DEFAULT_ALGO_OPTIONS : $hashOptionsObject + ['type' => $hash],
|
||||
'registration' => DateTime::now(),
|
||||
'reset' => false,
|
||||
'name' => $name,
|
||||
@@ -147,7 +126,7 @@ function createUser(Hash $hash, string $userId, ?string $email, ?string $passwor
|
||||
'search' => implode(' ', [$userId, $email, $phone, $name]),
|
||||
]);
|
||||
|
||||
if ($hash instanceof Plaintext) {
|
||||
if ($hash === 'plaintext') {
|
||||
$hooks->trigger('passwordValidator', [$dbForProject, $project, $plaintextPassword, &$user, true]);
|
||||
}
|
||||
|
||||
@@ -238,9 +217,7 @@ App::post('/v1/users')
|
||||
->inject('dbForProject')
|
||||
->inject('hooks')
|
||||
->action(function (string $userId, ?string $email, ?string $phone, ?string $password, string $name, Response $response, Document $project, Database $dbForProject, Hooks $hooks) {
|
||||
$plaintext = new Plaintext();
|
||||
|
||||
$user = createUser($plaintext, $userId, $email, $password, $phone, $name, $project, $dbForProject, $hooks);
|
||||
$user = createUser('plaintext', '{}', $userId, $email, $password, $phone, $name, $project, $dbForProject, $hooks);
|
||||
$response
|
||||
->setStatusCode(Response::STATUS_CODE_CREATED)
|
||||
->dynamic($user, Response::MODEL_USER);
|
||||
@@ -274,10 +251,7 @@ App::post('/v1/users/bcrypt')
|
||||
->inject('dbForProject')
|
||||
->inject('hooks')
|
||||
->action(function (string $userId, string $email, string $password, string $name, Response $response, Document $project, Database $dbForProject, Hooks $hooks) {
|
||||
$bcrypt = new Bcrypt();
|
||||
$bcrypt->setCost(8); // Default cost
|
||||
|
||||
$user = createUser($bcrypt, $userId, $email, $password, null, $name, $project, $dbForProject, $hooks);
|
||||
$user = createUser('bcrypt', '{}', $userId, $email, $password, null, $name, $project, $dbForProject, $hooks);
|
||||
|
||||
$response
|
||||
->setStatusCode(Response::STATUS_CODE_CREATED)
|
||||
@@ -312,9 +286,7 @@ App::post('/v1/users/md5')
|
||||
->inject('dbForProject')
|
||||
->inject('hooks')
|
||||
->action(function (string $userId, string $email, string $password, string $name, Response $response, Document $project, Database $dbForProject, Hooks $hooks) {
|
||||
$md5 = new MD5();
|
||||
|
||||
$user = createUser($md5, $userId, $email, $password, null, $name, $project, $dbForProject, $hooks);
|
||||
$user = createUser('md5', '{}', $userId, $email, $password, null, $name, $project, $dbForProject, $hooks);
|
||||
|
||||
$response
|
||||
->setStatusCode(Response::STATUS_CODE_CREATED)
|
||||
@@ -349,13 +321,7 @@ App::post('/v1/users/argon2')
|
||||
->inject('dbForProject')
|
||||
->inject('hooks')
|
||||
->action(function (string $userId, string $email, string $password, string $name, Response $response, Document $project, Database $dbForProject, Hooks $hooks) {
|
||||
$argon2 = new Argon2();
|
||||
$argon2
|
||||
->setMemoryCost(2048)
|
||||
->setTimeCost(4)
|
||||
->setThreads(3);
|
||||
|
||||
$user = createUser($argon2, $userId, $email, $password, null, $name, $project, $dbForProject, $hooks);
|
||||
$user = createUser('argon2', '{}', $userId, $email, $password, null, $name, $project, $dbForProject, $hooks);
|
||||
|
||||
$response
|
||||
->setStatusCode(Response::STATUS_CODE_CREATED)
|
||||
@@ -391,12 +357,13 @@ App::post('/v1/users/sha')
|
||||
->inject('dbForProject')
|
||||
->inject('hooks')
|
||||
->action(function (string $userId, string $email, string $password, string $passwordVersion, string $name, Response $response, Document $project, Database $dbForProject, Hooks $hooks) {
|
||||
$sha = new Sha();
|
||||
$options = '{}';
|
||||
|
||||
if (!empty($passwordVersion)) {
|
||||
$sha->setVersion($passwordVersion);
|
||||
$options = '{"version":"' . $passwordVersion . '"}';
|
||||
}
|
||||
|
||||
$user = createUser($sha, $userId, $email, $password, null, $name, $project, $dbForProject, $hooks);
|
||||
$user = createUser('sha', $options, $userId, $email, $password, null, $name, $project, $dbForProject, $hooks);
|
||||
|
||||
$response
|
||||
->setStatusCode(Response::STATUS_CODE_CREATED)
|
||||
@@ -431,9 +398,7 @@ App::post('/v1/users/phpass')
|
||||
->inject('dbForProject')
|
||||
->inject('hooks')
|
||||
->action(function (string $userId, string $email, string $password, string $name, Response $response, Document $project, Database $dbForProject, Hooks $hooks) {
|
||||
$phpass = new PHPass();
|
||||
|
||||
$user = createUser($phpass, $userId, $email, $password, null, $name, $project, $dbForProject, $hooks);
|
||||
$user = createUser('phpass', '{}', $userId, $email, $password, null, $name, $project, $dbForProject, $hooks);
|
||||
|
||||
$response
|
||||
->setStatusCode(Response::STATUS_CODE_CREATED)
|
||||
@@ -473,15 +438,15 @@ App::post('/v1/users/scrypt')
|
||||
->inject('dbForProject')
|
||||
->inject('hooks')
|
||||
->action(function (string $userId, string $email, string $password, string $passwordSalt, int $passwordCpu, int $passwordMemory, int $passwordParallel, int $passwordLength, string $name, Response $response, Document $project, Database $dbForProject, Hooks $hooks) {
|
||||
$scrypt = new Scrypt();
|
||||
$scrypt
|
||||
->setSalt($passwordSalt)
|
||||
->setCpuCost($passwordCpu)
|
||||
->setMemoryCost($passwordMemory)
|
||||
->setParallelCost($passwordParallel)
|
||||
->setLength($passwordLength);
|
||||
$options = [
|
||||
'salt' => $passwordSalt,
|
||||
'costCpu' => $passwordCpu,
|
||||
'costMemory' => $passwordMemory,
|
||||
'costParallel' => $passwordParallel,
|
||||
'length' => $passwordLength
|
||||
];
|
||||
|
||||
$user = createUser($scrypt, $userId, $email, $password, null, $name, $project, $dbForProject, $hooks);
|
||||
$user = createUser('scrypt', \json_encode($options), $userId, $email, $password, null, $name, $project, $dbForProject, $hooks);
|
||||
|
||||
$response
|
||||
->setStatusCode(Response::STATUS_CODE_CREATED)
|
||||
@@ -519,13 +484,7 @@ App::post('/v1/users/scrypt-modified')
|
||||
->inject('dbForProject')
|
||||
->inject('hooks')
|
||||
->action(function (string $userId, string $email, string $password, string $passwordSalt, string $passwordSaltSeparator, string $passwordSignerKey, string $name, Response $response, Document $project, Database $dbForProject, Hooks $hooks) {
|
||||
$scryptModified = new ScryptModified();
|
||||
$scryptModified
|
||||
->setSalt($passwordSalt)
|
||||
->setSaltSeparator($passwordSaltSeparator)
|
||||
->setSignerKey($passwordSignerKey);
|
||||
|
||||
$user = createUser($scryptModified, $userId, $email, $password, null, $name, $project, $dbForProject, $hooks);
|
||||
$user = createUser('scryptMod', '{"signerKey":"' . $passwordSignerKey . '","saltSeparator":"' . $passwordSaltSeparator . '","salt":"' . $passwordSalt . '"}', $userId, $email, $password, null, $name, $project, $dbForProject, $hooks);
|
||||
|
||||
$response
|
||||
->setStatusCode(Response::STATUS_CODE_CREATED)
|
||||
@@ -1110,6 +1069,7 @@ App::get('/v1/users/identities')
|
||||
} catch (QueryException $e) {
|
||||
throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage());
|
||||
}
|
||||
|
||||
if (!empty($search)) {
|
||||
$queries[] = Query::search('search', $search);
|
||||
}
|
||||
@@ -1376,21 +1336,12 @@ App::patch('/v1/users/:userId/password')
|
||||
|
||||
$hooks->trigger('passwordValidator', [$dbForProject, $project, $password, &$user, true]);
|
||||
|
||||
// Create Argon2 hasher with default settings
|
||||
$hasher = new Argon2();
|
||||
$hasher
|
||||
->setMemoryCost(2048)
|
||||
->setTimeCost(4)
|
||||
->setThreads(3);
|
||||
$newPassword = Auth::passwordHash($password, Auth::DEFAULT_ALGO, Auth::DEFAULT_ALGO_OPTIONS);
|
||||
|
||||
$newPassword = $hasher->hash($password);
|
||||
|
||||
$hash = ProofsPassword::createHash($user->getAttribute('hash'), $user->getAttribute('hashOptions'));
|
||||
$historyLimit = $project->getAttribute('auths', [])['passwordHistory'] ?? 0;
|
||||
$history = $user->getAttribute('passwordHistory', []);
|
||||
|
||||
if ($historyLimit > 0) {
|
||||
$validator = new PasswordHistory($history, $hash);
|
||||
$validator = new PasswordHistory($history, $user->getAttribute('hash'), $user->getAttribute('hashOptions'));
|
||||
if (!$validator->isValid($password)) {
|
||||
throw new Exception(Exception::USER_PASSWORD_RECENTLY_USED);
|
||||
}
|
||||
@@ -1403,8 +1354,8 @@ App::patch('/v1/users/:userId/password')
|
||||
->setAttribute('password', $newPassword)
|
||||
->setAttribute('passwordHistory', $history)
|
||||
->setAttribute('passwordUpdate', DateTime::now())
|
||||
->setAttribute('hash', $hasher->getName())
|
||||
->setAttribute('hashOptions', $hasher->getOptions());
|
||||
->setAttribute('hash', Auth::DEFAULT_ALGO)
|
||||
->setAttribute('hashOptions', Auth::DEFAULT_ALGO_OPTIONS);
|
||||
|
||||
$user = $dbForProject->updateDocument('users', $user->getId(), $user);
|
||||
|
||||
@@ -2217,19 +2168,17 @@ App::post('/v1/users/:userId/sessions')
|
||||
->inject('locale')
|
||||
->inject('geodb')
|
||||
->inject('queueForEvents')
|
||||
->inject('store')
|
||||
->inject('proofForToken')
|
||||
->action(function (string $userId, Request $request, Response $response, Database $dbForProject, Document $project, Locale $locale, Reader $geodb, Event $queueForEvents, Store $store, Token $proofForToken) {
|
||||
->action(function (string $userId, Request $request, Response $response, Database $dbForProject, Document $project, Locale $locale, Reader $geodb, Event $queueForEvents) {
|
||||
$user = $dbForProject->getDocument('users', $userId);
|
||||
if ($user->isEmpty()) {
|
||||
throw new Exception(Exception::USER_NOT_FOUND);
|
||||
}
|
||||
|
||||
$secret = $proofForToken->generate();
|
||||
$secret = Auth::tokenGenerator(Auth::TOKEN_LENGTH_SESSION);
|
||||
$detector = new Detector($request->getUserAgent('UNKNOWN'));
|
||||
$record = $geodb->get($request->getIP());
|
||||
|
||||
$duration = $project->getAttribute('auths', [])['duration'] ?? TOKEN_EXPIRATION_LOGIN_LONG;
|
||||
$duration = $project->getAttribute('auths', [])['duration'] ?? Auth::TOKEN_EXPIRATION_LOGIN_LONG;
|
||||
$expire = DateTime::formatTz(DateTime::addSeconds(new \DateTime(), $duration));
|
||||
|
||||
$session = new Document(array_merge(
|
||||
@@ -2237,8 +2186,8 @@ App::post('/v1/users/:userId/sessions')
|
||||
'$id' => ID::unique(),
|
||||
'userId' => $user->getId(),
|
||||
'userInternalId' => $user->getSequence(),
|
||||
'provider' => SESSION_PROVIDER_SERVER,
|
||||
'secret' => $proofForToken->hash($secret), // One way hash encryption to protect DB leak
|
||||
'provider' => Auth::SESSION_PROVIDER_SERVER,
|
||||
'secret' => Auth::hash($secret), // One way hash encryption to protect DB leak
|
||||
'userAgent' => $request->getUserAgent('UNKNOWN'),
|
||||
'factors' => ['server'],
|
||||
'ip' => $request->getIP(),
|
||||
@@ -2262,13 +2211,8 @@ App::post('/v1/users/:userId/sessions')
|
||||
|
||||
$dbForProject->purgeCachedDocument('users', $user->getId());
|
||||
|
||||
$encoded = $store
|
||||
->setProperty('id', $user->getId())
|
||||
->setProperty('secret', $secret)
|
||||
->encode();
|
||||
|
||||
$session
|
||||
->setAttribute('secret', $encoded)
|
||||
->setAttribute('secret', Auth::encodeSession($user->getId(), $secret))
|
||||
->setAttribute('countryName', $countryName);
|
||||
|
||||
$queueForEvents
|
||||
@@ -2303,7 +2247,7 @@ App::post('/v1/users/:userId/tokens')
|
||||
))
|
||||
->param('userId', '', new UID(), 'User ID.')
|
||||
->param('length', 6, new Range(4, 128), 'Token length in characters. The default length is 6 characters', true)
|
||||
->param('expire', TOKEN_EXPIRATION_GENERIC, new Range(60, TOKEN_EXPIRATION_LOGIN_LONG), 'Token expiration period in seconds. The default expiration is 15 minutes.', true)
|
||||
->param('expire', Auth::TOKEN_EXPIRATION_GENERIC, new Range(60, Auth::TOKEN_EXPIRATION_LOGIN_LONG), 'Token expiration period in seconds. The default expiration is 15 minutes.', true)
|
||||
->inject('request')
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
@@ -2315,17 +2259,15 @@ App::post('/v1/users/:userId/tokens')
|
||||
throw new Exception(Exception::USER_NOT_FOUND);
|
||||
}
|
||||
|
||||
$proofForToken = new Token($length);
|
||||
$proofForToken->setHash(new Sha());
|
||||
$secret = $proofForToken->generate();
|
||||
$secret = Auth::tokenGenerator($length);
|
||||
$expire = DateTime::formatTz(DateTime::addSeconds(new \DateTime(), $expire));
|
||||
|
||||
$token = new Document([
|
||||
'$id' => ID::unique(),
|
||||
'userId' => $user->getId(),
|
||||
'userInternalId' => $user->getSequence(),
|
||||
'type' => TOKEN_TYPE_GENERIC,
|
||||
'secret' => $proofForToken->hash($secret),
|
||||
'type' => Auth::TOKEN_TYPE_GENERIC,
|
||||
'secret' => Auth::hash($secret),
|
||||
'expire' => $expire,
|
||||
'userAgent' => $request->getUserAgent('UNKNOWN'),
|
||||
'ip' => $request->getIP()
|
||||
|
||||
@@ -1185,29 +1185,17 @@ App::error()
|
||||
$trace = $error->getTrace();
|
||||
|
||||
if (php_sapi_name() === 'cli') {
|
||||
$logLevel = $code >= 500 || $code == 0 ? 'error' : 'warning';
|
||||
$logPrefix = $code >= 500 || $code == 0 ? '[Error]' : '[Warning]';
|
||||
|
||||
Console::{$logLevel}($logPrefix . ' Timestamp: ' . date('c', time()));
|
||||
Console::error('[Error] Timestamp: ' . date('c', time()));
|
||||
|
||||
if ($route) {
|
||||
Console::{$logLevel}($logPrefix . ' Status Code: ' . $code);
|
||||
Console::{$logLevel}($logPrefix . ' URL: ' . $route->getMethod() . ' ' . $route->getPath());
|
||||
Console::error('[Error] Method: ' . $route->getMethod());
|
||||
Console::error('[Error] URL: ' . $route->getPath());
|
||||
}
|
||||
Console::{$logLevel}($logPrefix . ' Type: ' . get_class($error));
|
||||
Console::{$logLevel}($logPrefix . ' Message: ' . $message);
|
||||
Console::{$logLevel}($logPrefix . ' File: ' . $file);
|
||||
Console::{$logLevel}($logPrefix . ' Line: ' . $line);
|
||||
Console::{$logLevel}($logPrefix . ' Trace:');
|
||||
foreach ($trace as $index => $entry) {
|
||||
$traceFile = $entry['file'] ?? 'unknown';
|
||||
$traceLine = $entry['line'] ?? 0;
|
||||
$traceFunction = $entry['function'] ?? 'unknown';
|
||||
$traceClass = $entry['class'] ?? '';
|
||||
$traceType = $entry['type'] ?? '';
|
||||
Console::{$logLevel}(" #{$index} {$traceFile}({$traceLine}): {$traceClass}{$traceType}{$traceFunction}()");
|
||||
}
|
||||
Console::{$logLevel}('');
|
||||
|
||||
Console::error('[Error] Type: ' . get_class($error));
|
||||
Console::error('[Error] Message: ' . $message);
|
||||
Console::error('[Error] File: ' . $file);
|
||||
Console::error('[Error] Line: ' . $line);
|
||||
}
|
||||
|
||||
switch ($class) {
|
||||
|
||||
@@ -222,94 +222,39 @@ App::init()
|
||||
->action(function (App $utopia, Request $request, Database $dbForPlatform, Database $dbForProject, Audit $queueForAudits, Document $project, Document $user, ?Document $session, array $servers, string $mode, Document $team, ?Key $apiKey) {
|
||||
$route = $utopia->getRoute();
|
||||
|
||||
/**
|
||||
* Handle user authentication and session validation.
|
||||
*
|
||||
* This function follows a series of steps to determine the appropriate user session
|
||||
* based on cookies, headers, and JWT tokens.
|
||||
*
|
||||
* Process:
|
||||
*
|
||||
* Project & Role Validation:
|
||||
* 1. Check if the project is empty. If so, throw an exception.
|
||||
* 2. Get the roles configuration.
|
||||
* 3. Determine the role for the user based on the user document.
|
||||
* 4. Get the scopes for the role.
|
||||
*
|
||||
* API Key Authentication:
|
||||
* 5. If there is an API key:
|
||||
* - Verify no user session exists simultaneously
|
||||
* - Check if key is expired
|
||||
* - Set role and scopes from API key
|
||||
* - Handle special app role case
|
||||
* - For standard keys, update last accessed time
|
||||
*
|
||||
* User Activity:
|
||||
* 6. If the project is not the console and user is not admin:
|
||||
* - Update user's last activity timestamp
|
||||
*
|
||||
* Access Control:
|
||||
* 7. Get the method from the route
|
||||
* 8. Validate namespace permissions
|
||||
* 9. Validate scope permissions
|
||||
* 10. Check if user is blocked
|
||||
*
|
||||
* Security Checks:
|
||||
* 11. Verify password status (check if reset required)
|
||||
* 12. Validate MFA requirements:
|
||||
* - Check if MFA is enabled
|
||||
* - Verify email status
|
||||
* - Verify phone status
|
||||
* - Verify authenticator status
|
||||
* 13. Handle Multi-Factor Authentication:
|
||||
* - Check remaining required factors
|
||||
* - Validate factor completion
|
||||
* - Throw exception if factors incomplete
|
||||
*/
|
||||
|
||||
// Step 1: Check if project is empty
|
||||
if ($project->isEmpty()) {
|
||||
throw new Exception(Exception::PROJECT_NOT_FOUND);
|
||||
}
|
||||
|
||||
// Step 2: Get roles configuration
|
||||
$roles = Config::getParam('roles', []);
|
||||
|
||||
// Step 3: Determine role for user
|
||||
// TODO get scopes from the identity instead of the user roles config. The identity will containn the scopes the user authorized for the access token.
|
||||
|
||||
$role = $user->isEmpty()
|
||||
? Role::guests()->toString()
|
||||
: Role::users()->toString();
|
||||
|
||||
// Step 4: Get scopes for the role
|
||||
$scopes = $roles[$role]['scopes'];
|
||||
|
||||
// Step 5: API Key Authentication
|
||||
// API Key authentication
|
||||
if (!empty($apiKey)) {
|
||||
// Verify no user session exists simultaneously
|
||||
if (!$user->isEmpty()) {
|
||||
throw new Exception(Exception::USER_API_KEY_AND_SESSION_SET);
|
||||
}
|
||||
// Check if key is expired
|
||||
if ($apiKey->isExpired()) {
|
||||
throw new Exception(Exception::PROJECT_KEY_EXPIRED);
|
||||
}
|
||||
|
||||
// Set role and scopes from API key
|
||||
$role = $apiKey->getRole();
|
||||
$scopes = $apiKey->getScopes();
|
||||
|
||||
|
||||
// Handle special app role case
|
||||
if ($apiKey->getRole() === USER_ROLE_APPS) {
|
||||
if ($apiKey->getRole() === Auth::USER_ROLE_APPS) {
|
||||
// Disable authorization checks for API keys
|
||||
Authorization::setDefaultStatus(false);
|
||||
|
||||
$user = new Document([
|
||||
'$id' => '',
|
||||
'status' => true,
|
||||
'type' => ACTIVITY_TYPE_APP,
|
||||
'type' => Auth::ACTIVITY_TYPE_APP,
|
||||
'email' => 'app.' . $project->getId() . '@service.' . $request->getHostname(),
|
||||
'password' => '',
|
||||
'name' => $apiKey->getName(),
|
||||
@@ -318,7 +263,6 @@ App::init()
|
||||
$queueForAudits->setUser($user);
|
||||
}
|
||||
|
||||
// For standard keys, update last accessed time
|
||||
if ($apiKey->getType() === API_KEY_STANDARD) {
|
||||
$dbKey = $project->find(
|
||||
key: 'secret',
|
||||
@@ -388,7 +332,7 @@ App::init()
|
||||
Authorization::setRole($authRole);
|
||||
}
|
||||
|
||||
// Step 6: Update project and user last activity
|
||||
// Update project last activity
|
||||
if (!$project->isEmpty() && $project->getId() !== 'console') {
|
||||
$accessedAt = $project->getAttribute('accessedAt', 0);
|
||||
if (DateTime::formatTz(DateTime::addSeconds(new \DateTime(), -APP_PROJECT_ACCESS)) > $accessedAt) {
|
||||
@@ -397,6 +341,7 @@ App::init()
|
||||
}
|
||||
}
|
||||
|
||||
// Update user last activity
|
||||
if (!empty($user->getId())) {
|
||||
$accessedAt = $user->getAttribute('accessedAt', 0);
|
||||
if (DateTime::formatTz(DateTime::addSeconds(new \DateTime(), -APP_USER_ACCESS)) > $accessedAt) {
|
||||
@@ -410,7 +355,6 @@ App::init()
|
||||
}
|
||||
}
|
||||
|
||||
// Steps 7-9: Access Control - Method, Namespace and Scope Validation
|
||||
/**
|
||||
* @var ?Method $method
|
||||
*/
|
||||
@@ -434,23 +378,21 @@ App::init()
|
||||
}
|
||||
}
|
||||
|
||||
// Step 9: Validate scope permissions
|
||||
// Do now allow access if scope is not allowed
|
||||
$allowed = (array)$route->getLabel('scope', 'none');
|
||||
if (empty(\array_intersect($allowed, $scopes))) {
|
||||
throw new Exception(Exception::GENERAL_UNAUTHORIZED_SCOPE, $user->getAttribute('email', 'User') . ' (role: ' . \strtolower($roles[$role]['label']) . ') missing scopes (' . \json_encode($allowed) . ')');
|
||||
}
|
||||
|
||||
// Step 10: Check if user is blocked
|
||||
// Do not allow access to blocked accounts
|
||||
if (false === $user->getAttribute('status')) { // Account is blocked
|
||||
throw new Exception(Exception::USER_BLOCKED);
|
||||
}
|
||||
|
||||
// Step 11: Verify password status
|
||||
if ($user->getAttribute('reset')) {
|
||||
throw new Exception(Exception::USER_PASSWORD_RESET_REQUIRED);
|
||||
}
|
||||
|
||||
// Step 12: Validate MFA requirements
|
||||
$mfaEnabled = $user->getAttribute('mfa', false);
|
||||
$hasVerifiedEmail = $user->getAttribute('emailVerification', false);
|
||||
$hasVerifiedPhone = $user->getAttribute('phoneVerification', false);
|
||||
@@ -458,7 +400,6 @@ App::init()
|
||||
$hasMoreFactors = $hasVerifiedEmail || $hasVerifiedPhone || $hasVerifiedAuthenticator;
|
||||
$minimumFactors = ($mfaEnabled && $hasMoreFactors) ? 2 : 1;
|
||||
|
||||
// Step 13: Handle Multi-Factor Authentication
|
||||
if (!in_array('mfa', $route->getGroups())) {
|
||||
if ($session && \count($session->getAttribute('factors', [])) < $minimumFactors) {
|
||||
throw new Exception(Exception::USER_MORE_FACTORS_REQUIRED);
|
||||
@@ -585,7 +526,7 @@ App::init()
|
||||
if (!$user->isEmpty()) {
|
||||
$userClone = clone $user;
|
||||
// $user doesn't support `type` and can cause unintended effects.
|
||||
$userClone->setAttribute('type', ACTIVITY_TYPE_USER);
|
||||
$userClone->setAttribute('type', Auth::ACTIVITY_TYPE_USER);
|
||||
$queueForAudits->setUser($userClone);
|
||||
}
|
||||
|
||||
@@ -825,7 +766,7 @@ App::shutdown()
|
||||
if (!$user->isEmpty()) {
|
||||
$userClone = clone $user;
|
||||
// $user doesn't support `type` and can cause unintended effects.
|
||||
$userClone->setAttribute('type', ACTIVITY_TYPE_USER);
|
||||
$userClone->setAttribute('type', Auth::ACTIVITY_TYPE_USER);
|
||||
$queueForAudits->setUser($userClone);
|
||||
} elseif ($queueForAudits->getUser() === null || $queueForAudits->getUser()->isEmpty()) {
|
||||
/**
|
||||
@@ -839,7 +780,7 @@ App::shutdown()
|
||||
$user = new Document([
|
||||
'$id' => '',
|
||||
'status' => true,
|
||||
'type' => ACTIVITY_TYPE_GUEST,
|
||||
'type' => Auth::ACTIVITY_TYPE_GUEST,
|
||||
'email' => 'guest.' . $project->getId() . '@service.' . $request->getHostname(),
|
||||
'password' => '',
|
||||
'name' => 'Guest',
|
||||
|
||||
@@ -20,7 +20,7 @@ App::init()
|
||||
$lastUpdate = $session->getAttribute('mfaUpdatedAt');
|
||||
if (!empty($lastUpdate)) {
|
||||
$now = DateTime::now();
|
||||
$maxAllowedDate = DateTime::addSeconds(new \DateTime($lastUpdate), MFA_RECENT_DURATION); // Maximum date until session is considered safe before asking for another challenge
|
||||
$maxAllowedDate = DateTime::addSeconds(new \DateTime($lastUpdate), Auth::MFA_RECENT_DURATION); // Maximum date until session is considered safe before asking for another challenge
|
||||
|
||||
$isSessionFresh = DateTime::formatTz($maxAllowedDate) >= DateTime::formatTz($now);
|
||||
}
|
||||
|
||||
@@ -92,72 +92,6 @@ const APP_VCS_GITHUB_USERNAME = 'Appwrite';
|
||||
const APP_VCS_GITHUB_EMAIL = 'team@appwrite.io';
|
||||
const APP_BRANDED_EMAIL_BASE_TEMPLATE = 'email-base-styled';
|
||||
|
||||
// User Roles
|
||||
const USER_ROLE_ANY = 'any';
|
||||
const USER_ROLE_GUESTS = 'guests';
|
||||
const USER_ROLE_USERS = 'users';
|
||||
const USER_ROLE_ADMIN = 'admin';
|
||||
const USER_ROLE_DEVELOPER = 'developer';
|
||||
const USER_ROLE_OWNER = 'owner';
|
||||
const USER_ROLE_APPS = 'apps';
|
||||
const USER_ROLE_SYSTEM = 'system';
|
||||
|
||||
/**
|
||||
* Token Expiration times.
|
||||
*/
|
||||
const TOKEN_EXPIRATION_LOGIN_LONG = 31536000; /* 1 year */
|
||||
const TOKEN_EXPIRATION_LOGIN_SHORT = 3600; /* 1 hour */
|
||||
const TOKEN_EXPIRATION_RECOVERY = 3600; /* 1 hour */
|
||||
const TOKEN_EXPIRATION_CONFIRM = 3600 * 1; /* 1 hour */
|
||||
const TOKEN_EXPIRATION_OTP = 60 * 15; /* 15 minutes */
|
||||
const TOKEN_EXPIRATION_GENERIC = 60 * 15; /* 15 minutes */
|
||||
|
||||
/**
|
||||
* Token Lengths.
|
||||
*/
|
||||
const TOKEN_LENGTH_MAGIC_URL = 64;
|
||||
const TOKEN_LENGTH_VERIFICATION = 256;
|
||||
const TOKEN_LENGTH_RECOVERY = 256;
|
||||
const TOKEN_LENGTH_OAUTH2 = 64;
|
||||
const TOKEN_LENGTH_SESSION = 256;
|
||||
|
||||
/**
|
||||
* Token Types.
|
||||
*/
|
||||
const TOKEN_TYPE_LOGIN = 1; // Deprecated
|
||||
const TOKEN_TYPE_VERIFICATION = 2;
|
||||
const TOKEN_TYPE_RECOVERY = 3;
|
||||
const TOKEN_TYPE_INVITE = 4;
|
||||
const TOKEN_TYPE_MAGIC_URL = 5;
|
||||
const TOKEN_TYPE_PHONE = 6;
|
||||
const TOKEN_TYPE_OAUTH2 = 7;
|
||||
const TOKEN_TYPE_GENERIC = 8;
|
||||
const TOKEN_TYPE_EMAIL = 9; // OTP
|
||||
|
||||
/**
|
||||
* Session Providers.
|
||||
*/
|
||||
const SESSION_PROVIDER_EMAIL = 'email';
|
||||
const SESSION_PROVIDER_ANONYMOUS = 'anonymous';
|
||||
const SESSION_PROVIDER_MAGIC_URL = 'magic-url';
|
||||
const SESSION_PROVIDER_PHONE = 'phone';
|
||||
const SESSION_PROVIDER_OAUTH2 = 'oauth2';
|
||||
const SESSION_PROVIDER_TOKEN = 'token';
|
||||
const SESSION_PROVIDER_SERVER = 'server';
|
||||
|
||||
/**
|
||||
* Activity associated with user or the app.
|
||||
*/
|
||||
const ACTIVITY_TYPE_APP = 'app';
|
||||
const ACTIVITY_TYPE_USER = 'user';
|
||||
const ACTIVITY_TYPE_GUEST = 'guest';
|
||||
|
||||
/**
|
||||
* MFA
|
||||
*/
|
||||
const MFA_RECENT_DURATION = 1800; // 30 mins
|
||||
|
||||
|
||||
// Database Reconnect
|
||||
const DATABASE_RECONNECT_SLEEP = 2;
|
||||
const DATABASE_RECONNECT_MAX_ATTEMPTS = 10;
|
||||
|
||||
+27
-86
@@ -24,16 +24,9 @@ use Appwrite\GraphQL\Schema;
|
||||
use Appwrite\Network\Platform;
|
||||
use Appwrite\Network\Validator\Origin;
|
||||
use Appwrite\Utopia\Request;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Executor\Executor;
|
||||
use Utopia\Abuse\Adapters\TimeLimit\Redis as TimeLimitRedis;
|
||||
use Utopia\App;
|
||||
use Utopia\Auth\Hashes\Argon2;
|
||||
use Utopia\Auth\Hashes\Sha;
|
||||
use Utopia\Auth\Proofs\Code;
|
||||
use Utopia\Auth\Proofs\Password;
|
||||
use Utopia\Auth\Proofs\Token;
|
||||
use Utopia\Auth\Store;
|
||||
use Utopia\Cache\Adapter\Pool as CachePool;
|
||||
use Utopia\Cache\Adapter\Sharding;
|
||||
use Utopia\Cache\Cache;
|
||||
@@ -233,93 +226,72 @@ App::setResource('platforms', function (Request $request, Document $console, Doc
|
||||
];
|
||||
}, ['request', 'console', 'project', 'dbForPlatform']);
|
||||
|
||||
App::setResource('user', function (string $mode, Document $project, Document $console, Request $request, Response $response, Database $dbForProject, Database $dbForPlatform, Store $store, Token $proofForToken) {
|
||||
App::setResource('user', function ($mode, $project, $console, $request, $response, $dbForProject, $dbForPlatform) {
|
||||
/** @var Appwrite\Utopia\Request $request */
|
||||
/** @var Appwrite\Utopia\Response $response */
|
||||
/** @var Utopia\Database\Document $project */
|
||||
/** @var Utopia\Database\Database $dbForProject */
|
||||
/** @var Utopia\Database\Database $dbForPlatform */
|
||||
/** @var string $mode */
|
||||
/** @var Utopia\Auth\Store $store */
|
||||
|
||||
/**
|
||||
* Handles user authentication and session validation.
|
||||
*
|
||||
* This function follows a series of steps to determine the appropriate user session
|
||||
* based on cookies, headers, and JWT tokens.
|
||||
*
|
||||
* Process:
|
||||
* 1. Checks the cookie based on mode:
|
||||
* - If in admin mode, uses console project id for key.
|
||||
* - Otherwise, sets the key using the project ID
|
||||
* 2. If no cookie is found, attempts to retrieve the fallback header `x-fallback-cookies`.
|
||||
* - If this method is used, returns the header: `X-Debug-Fallback: true`.
|
||||
* 3. Fetches the user document from the appropriate database based on the mode.
|
||||
* 4. If the user document is empty or the session key cannot be verified, sets an empty user document.
|
||||
* 5. Regardless of the results from steps 1-4, attempts to fetch the JWT token.
|
||||
* 6. If the JWT user has a valid session ID, updates the user variable with the user from `projectDB`,
|
||||
* overwriting the previous value.
|
||||
*/
|
||||
|
||||
Authorization::setDefaultStatus(true);
|
||||
|
||||
$store->setKey('a_session_' . $project->getId());
|
||||
Auth::setCookieName('a_session_' . $project->getId());
|
||||
|
||||
if (APP_MODE_ADMIN === $mode) {
|
||||
$store->setKey('a_session_' . $console->getId());
|
||||
Auth::setCookieName('a_session_' . $console->getId());
|
||||
}
|
||||
|
||||
$store->decode(
|
||||
$session = Auth::decodeSession(
|
||||
$request->getCookie(
|
||||
$store->getKey(), // Get sessions
|
||||
$request->getCookie($store->getKey() . '_legacy', '')
|
||||
Auth::$cookieName, // Get sessions
|
||||
$request->getCookie(Auth::$cookieName . '_legacy', '')
|
||||
)
|
||||
);
|
||||
|
||||
// Get session from header for SSR clients
|
||||
if (empty($store->getProperty('id', '')) && empty($store->getProperty('secret', ''))) {
|
||||
if (empty($session['id']) && empty($session['secret'])) {
|
||||
$sessionHeader = $request->getHeader('x-appwrite-session', '');
|
||||
|
||||
if (!empty($sessionHeader)) {
|
||||
$store->decode($sessionHeader);
|
||||
$session = Auth::decodeSession($sessionHeader);
|
||||
}
|
||||
}
|
||||
|
||||
// Get fallback session from old clients (no SameSite support) or clients who block 3rd-party cookies
|
||||
if ($response) { // if in http context - add debug header
|
||||
if ($response) {
|
||||
$response->addHeader('X-Debug-Fallback', 'false');
|
||||
}
|
||||
|
||||
if (empty($store->getProperty('id', '')) && empty($store->getProperty('secret', ''))) {
|
||||
if (empty($session['id']) && empty($session['secret'])) {
|
||||
if ($response) {
|
||||
$response->addHeader('X-Debug-Fallback', 'true');
|
||||
}
|
||||
$fallback = $request->getHeader('x-fallback-cookies', '');
|
||||
$fallback = \json_decode($fallback, true);
|
||||
$store->decode(((is_array($fallback) && isset($fallback[$store->getKey()])) ? $fallback[$store->getKey()] : ''));
|
||||
$session = Auth::decodeSession(((isset($fallback[Auth::$cookieName])) ? $fallback[Auth::$cookieName] : ''));
|
||||
}
|
||||
|
||||
Auth::$unique = $session['id'] ?? '';
|
||||
Auth::$secret = $session['secret'] ?? '';
|
||||
|
||||
$user = new Document([]);
|
||||
|
||||
if (APP_MODE_ADMIN === $mode) {
|
||||
$user = $dbForPlatform->getDocument('users', $store->getProperty('id', ''));
|
||||
} else {
|
||||
if ($project->isEmpty()) {
|
||||
$user = new Document([]);
|
||||
} else {
|
||||
if (!empty($store->getProperty('id', ''))) {
|
||||
if ($project->getId() === 'console') {
|
||||
$user = $dbForPlatform->getDocument('users', $store->getProperty('id', ''));
|
||||
} else {
|
||||
$user = $dbForProject->getDocument('users', $store->getProperty('id', ''));
|
||||
}
|
||||
if (!empty(Auth::$unique)) {
|
||||
if ($mode === APP_MODE_ADMIN) {
|
||||
$user = $dbForPlatform->getDocument('users', Auth::$unique);
|
||||
} elseif (!$project->isEmpty()) {
|
||||
if ($project->getId() === 'console') {
|
||||
$user = $dbForPlatform->getDocument('users', Auth::$unique);
|
||||
} else {
|
||||
$user = $dbForProject->getDocument('users', Auth::$unique);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
$user->isEmpty() // Check a document has been found in the DB
|
||||
|| !Auth::sessionVerify($user->getAttribute('sessions', []), $store->getProperty('secret', ''), $proofForToken)
|
||||
|| !Auth::sessionVerify($user->getAttribute('sessions', []), Auth::$secret)
|
||||
) { // Validate user has valid login token
|
||||
$user = new Document([]);
|
||||
}
|
||||
@@ -364,7 +336,7 @@ App::setResource('user', function (string $mode, Document $project, Document $co
|
||||
$dbForPlatform->setMetadata('user', $user->getId());
|
||||
|
||||
return $user;
|
||||
}, ['mode', 'project', 'console', 'request', 'response', 'dbForProject', 'dbForPlatform', 'store', 'proofForToken']);
|
||||
}, ['mode', 'project', 'console', 'request', 'response', 'dbForProject', 'dbForPlatform']);
|
||||
|
||||
App::setResource('project', function ($dbForPlatform, $request, $console) {
|
||||
/** @var Appwrite\Utopia\Request $request */
|
||||
@@ -382,13 +354,13 @@ App::setResource('project', function ($dbForPlatform, $request, $console) {
|
||||
return $project;
|
||||
}, ['dbForPlatform', 'request', 'console']);
|
||||
|
||||
App::setResource('session', function (Document $user, Store $store, Token $proofForToken) {
|
||||
App::setResource('session', function (Document $user) {
|
||||
if ($user->isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$sessions = $user->getAttribute('sessions', []);
|
||||
$sessionId = Auth::sessionVerify($user->getAttribute('sessions'), $store->getProperty('secret', ''), $proofForToken);
|
||||
$sessionId = Auth::sessionVerify($user->getAttribute('sessions'), Auth::$secret);
|
||||
|
||||
if (!$sessionId) {
|
||||
return;
|
||||
@@ -401,7 +373,7 @@ App::setResource('session', function (Document $user, Store $store, Token $proof
|
||||
}
|
||||
|
||||
return;
|
||||
}, ['user', 'store', 'proofForToken']);
|
||||
}, ['user']);
|
||||
|
||||
App::setResource('console', function () {
|
||||
return new Document(Config::getParam('console'));
|
||||
@@ -975,37 +947,6 @@ App::setResource('apiKey', function (Request $request, Document $project): ?Key
|
||||
return Key::decode($project, $key);
|
||||
}, ['request', 'project']);
|
||||
|
||||
|
||||
App::setResource('store', function (): Store {
|
||||
return new Store();
|
||||
});
|
||||
|
||||
App::setResource('proofForPassword', function (): Password {
|
||||
$hash = new Argon2();
|
||||
$hash
|
||||
->setMemoryCost(2048)
|
||||
->setTimeCost(4)
|
||||
->setThreads(3);
|
||||
|
||||
$password = new Password();
|
||||
$password
|
||||
->setHash($hash);
|
||||
|
||||
return $password;
|
||||
});
|
||||
|
||||
App::setResource('proofForToken', function (): Token {
|
||||
$token = new Token();
|
||||
$token->setHash(new Sha());
|
||||
return $token;
|
||||
});
|
||||
|
||||
App::setResource('proofForCode', function (): Code {
|
||||
$code = new Code();
|
||||
$code->setHash(new Sha());
|
||||
return $code;
|
||||
});
|
||||
|
||||
App::setResource('executor', fn () => new Executor());
|
||||
|
||||
App::setResource('resourceToken', function ($project, $dbForProject, $request) {
|
||||
|
||||
+5
-17
@@ -16,9 +16,6 @@ use Swoole\Timer;
|
||||
use Utopia\Abuse\Abuse;
|
||||
use Utopia\Abuse\Adapters\TimeLimit\Redis as TimeLimitRedis;
|
||||
use Utopia\App;
|
||||
use Utopia\Auth\Hashes\Sha;
|
||||
use Utopia\Auth\Proofs\Token;
|
||||
use Utopia\Auth\Store;
|
||||
use Utopia\Cache\Adapter\Pool as CachePool;
|
||||
use Utopia\Cache\Adapter\Sharding;
|
||||
use Utopia\Cache\Cache;
|
||||
@@ -681,24 +678,15 @@ $server->onMessage(function (int $connection, string $message) use ($server, $re
|
||||
throw new Exception(Exception::REALTIME_MESSAGE_FORMAT_INVALID, 'Payload is not valid.');
|
||||
}
|
||||
|
||||
$store = new Store();
|
||||
$session = Auth::decodeSession($message['data']['session']);
|
||||
Auth::$unique = $session['id'] ?? '';
|
||||
Auth::$secret = $session['secret'] ?? '';
|
||||
|
||||
$store->decode($message['data']['session']);
|
||||
|
||||
$user = $database->getDocument('users', $store->getProperty('id', ''));
|
||||
|
||||
/**
|
||||
* TODO:
|
||||
* Moving forward, we should try to use our dependency injection container
|
||||
* to inject the proof for token.
|
||||
* This way we will have one source of truth for the proof for token.
|
||||
*/
|
||||
$proofForToken = new Token();
|
||||
$proofForToken->setHash(new Sha());
|
||||
$user = $database->getDocument('users', Auth::$unique);
|
||||
|
||||
if (
|
||||
empty($user->getId()) // Check a document has been found in the DB
|
||||
|| !Auth::sessionVerify($user->getAttribute('sessions', []), $store->getProperty('secret', ''), $proofForToken) // Validate user has valid login token
|
||||
|| !Auth::sessionVerify($user->getAttribute('sessions', []), Auth::$secret) // Validate user has valid login token
|
||||
) {
|
||||
// cookie not valid
|
||||
throw new Exception(Exception::REALTIME_MESSAGE_FORMAT_INVALID, 'Session is not valid.');
|
||||
|
||||
@@ -179,7 +179,7 @@ $image = $this->getParam('image', '');
|
||||
appwrite-console:
|
||||
<<: *x-logging
|
||||
container_name: appwrite-console
|
||||
image: <?php echo $organization; ?>/console:7.0.2
|
||||
image: <?php echo $organization; ?>/console:7.4.7
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- appwrite
|
||||
|
||||
+2
-3
@@ -46,16 +46,15 @@
|
||||
"ext-sockets": "*",
|
||||
"appwrite/php-runtimes": "0.19.*",
|
||||
"appwrite/php-clamav": "2.0.*",
|
||||
"utopia-php/auth": "0.4.*",
|
||||
"utopia-php/abuse": "1.*",
|
||||
"utopia-php/analytics": "0.10.*",
|
||||
"utopia-php/audit": "1.*",
|
||||
"utopia-php/cache": "0.13.*",
|
||||
"utopia-php/cli": "0.15.*",
|
||||
"utopia-php/config": "0.2.*",
|
||||
"utopia-php/database": "2.*",
|
||||
"utopia-php/database": "3.*",
|
||||
"utopia-php/detector": "0.2.*",
|
||||
"utopia-php/domains": "0.8.*",
|
||||
"utopia-php/domains": "0.9.*",
|
||||
"utopia-php/dns": "0.3.*",
|
||||
"utopia-php/dsn": "0.2.1",
|
||||
"utopia-php/framework": "0.33.*",
|
||||
|
||||
+1
-1
@@ -219,7 +219,7 @@ services:
|
||||
appwrite-console:
|
||||
<<: *x-logging
|
||||
container_name: appwrite-console
|
||||
image: appwrite/console:7.0.2
|
||||
image: appwrite/console:7.4.7
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- appwrite
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
# Change Log
|
||||
|
||||
## 13.3.0
|
||||
|
||||
* Add `onOpen`, `onClose` and `onError` callbacks to `Realtime` service
|
||||
|
||||
## 13.2.2
|
||||
|
||||
* Fix issue: Missing AppwriteEnums dependency causing build failure
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
# Change Log
|
||||
|
||||
## 10.2.2
|
||||
|
||||
* Fix `logout` command showing duplicate sessions
|
||||
* Fix `logout` command showing a blank email even when logged out
|
||||
* Add syncing of `tablesDB` resource during `push tables` command
|
||||
|
||||
## 10.2.1
|
||||
|
||||
* Add transaction support for Databases and TablesDB
|
||||
|
||||
+318
-33
@@ -2,8 +2,13 @@
|
||||
|
||||
namespace Appwrite\Auth;
|
||||
|
||||
use Utopia\Auth\Proof;
|
||||
use Utopia\Auth\Proofs\Token;
|
||||
use Appwrite\Auth\Hash\Argon2;
|
||||
use Appwrite\Auth\Hash\Bcrypt;
|
||||
use Appwrite\Auth\Hash\Md5;
|
||||
use Appwrite\Auth\Hash\Phpass;
|
||||
use Appwrite\Auth\Hash\Scrypt;
|
||||
use Appwrite\Auth\Hash\Scryptmodified;
|
||||
use Appwrite\Auth\Hash\Sha;
|
||||
use Utopia\Database\DateTime;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Helpers\Role;
|
||||
@@ -12,45 +17,186 @@ use Utopia\Database\Validator\Roles;
|
||||
|
||||
class Auth
|
||||
{
|
||||
public const SUPPORTED_ALGOS = [
|
||||
'argon2',
|
||||
'bcrypt',
|
||||
'md5',
|
||||
'sha',
|
||||
'phpass',
|
||||
'scrypt',
|
||||
'scryptMod',
|
||||
'plaintext'
|
||||
];
|
||||
|
||||
public const DEFAULT_ALGO = 'argon2';
|
||||
public const DEFAULT_ALGO_OPTIONS = ['type' => 'argon2', 'memoryCost' => 2048, 'timeCost' => 4, 'threads' => 3];
|
||||
|
||||
/**
|
||||
* User Roles.
|
||||
*/
|
||||
public const USER_ROLE_ANY = 'any';
|
||||
public const USER_ROLE_GUESTS = 'guests';
|
||||
public const USER_ROLE_USERS = 'users';
|
||||
public const USER_ROLE_ADMIN = 'admin';
|
||||
public const USER_ROLE_DEVELOPER = 'developer';
|
||||
public const USER_ROLE_OWNER = 'owner';
|
||||
public const USER_ROLE_APPS = 'apps';
|
||||
public const USER_ROLE_SYSTEM = 'system';
|
||||
|
||||
/**
|
||||
* Activity associated with user or the app.
|
||||
*/
|
||||
public const ACTIVITY_TYPE_APP = 'app';
|
||||
public const ACTIVITY_TYPE_USER = 'user';
|
||||
public const ACTIVITY_TYPE_GUEST = 'guest';
|
||||
|
||||
/**
|
||||
* Token Types.
|
||||
*/
|
||||
public const TOKEN_TYPE_LOGIN = 1; // Deprecated
|
||||
public const TOKEN_TYPE_VERIFICATION = 2;
|
||||
public const TOKEN_TYPE_RECOVERY = 3;
|
||||
public const TOKEN_TYPE_INVITE = 4;
|
||||
public const TOKEN_TYPE_MAGIC_URL = 5;
|
||||
public const TOKEN_TYPE_PHONE = 6;
|
||||
public const TOKEN_TYPE_OAUTH2 = 7;
|
||||
public const TOKEN_TYPE_GENERIC = 8;
|
||||
public const TOKEN_TYPE_EMAIL = 9; // OTP
|
||||
|
||||
/**
|
||||
* Session Providers.
|
||||
*/
|
||||
public const SESSION_PROVIDER_EMAIL = 'email';
|
||||
public const SESSION_PROVIDER_ANONYMOUS = 'anonymous';
|
||||
public const SESSION_PROVIDER_MAGIC_URL = 'magic-url';
|
||||
public const SESSION_PROVIDER_PHONE = 'phone';
|
||||
public const SESSION_PROVIDER_OAUTH2 = 'oauth2';
|
||||
public const SESSION_PROVIDER_TOKEN = 'token';
|
||||
public const SESSION_PROVIDER_SERVER = 'server';
|
||||
|
||||
/**
|
||||
* Token Expiration times.
|
||||
*/
|
||||
public const TOKEN_EXPIRATION_LOGIN_LONG = 31536000; /* 1 year */
|
||||
public const TOKEN_EXPIRATION_LOGIN_SHORT = 3600; /* 1 hour */
|
||||
public const TOKEN_EXPIRATION_RECOVERY = 3600; /* 1 hour */
|
||||
public const TOKEN_EXPIRATION_CONFIRM = 3600 * 1; /* 1 hour */
|
||||
public const TOKEN_EXPIRATION_OTP = 60 * 15; /* 15 minutes */
|
||||
public const TOKEN_EXPIRATION_GENERIC = 60 * 15; /* 15 minutes */
|
||||
|
||||
/**
|
||||
* Token Lengths.
|
||||
*/
|
||||
public const TOKEN_LENGTH_MAGIC_URL = 64;
|
||||
public const TOKEN_LENGTH_VERIFICATION = 256;
|
||||
public const TOKEN_LENGTH_RECOVERY = 256;
|
||||
public const TOKEN_LENGTH_OAUTH2 = 64;
|
||||
public const TOKEN_LENGTH_SESSION = 256;
|
||||
|
||||
/**
|
||||
* MFA
|
||||
*/
|
||||
public const MFA_RECENT_DURATION = 1800; // 30 mins
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public static $cookieName = 'a_session';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*
|
||||
* @deprecated We plan to deprecate this class in the future. Use Utopia Auth when possible.
|
||||
*/
|
||||
public static $cookieNamePreview = 'a_jwt_console';
|
||||
|
||||
/**
|
||||
* Token type to session provider mapping.
|
||||
* User Unique ID.
|
||||
*
|
||||
* @deprecated We plan to deprecate this class in the future. Use Utopia Auth when possible.
|
||||
* @param int $type
|
||||
* @var string
|
||||
*/
|
||||
public static $unique = '';
|
||||
|
||||
/**
|
||||
* User Secret Key.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public static $secret = '';
|
||||
|
||||
/**
|
||||
* Set Cookie Name.
|
||||
*
|
||||
* @param $string
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function setCookieName($string)
|
||||
{
|
||||
return self::$cookieName = $string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode Session.
|
||||
*
|
||||
* @param string $id
|
||||
* @param string $secret
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function encodeSession($id, $secret)
|
||||
{
|
||||
return \base64_encode(\json_encode([
|
||||
'id' => $id,
|
||||
'secret' => $secret,
|
||||
]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Token type to session provider mapping.
|
||||
*/
|
||||
public static function getSessionProviderByTokenType(int $type): string
|
||||
{
|
||||
switch ($type) {
|
||||
case TOKEN_TYPE_VERIFICATION:
|
||||
case TOKEN_TYPE_RECOVERY:
|
||||
case TOKEN_TYPE_INVITE:
|
||||
return SESSION_PROVIDER_EMAIL;
|
||||
case TOKEN_TYPE_MAGIC_URL:
|
||||
return SESSION_PROVIDER_MAGIC_URL;
|
||||
case TOKEN_TYPE_PHONE:
|
||||
return SESSION_PROVIDER_PHONE;
|
||||
case TOKEN_TYPE_OAUTH2:
|
||||
return SESSION_PROVIDER_OAUTH2;
|
||||
case Auth::TOKEN_TYPE_VERIFICATION:
|
||||
case Auth::TOKEN_TYPE_RECOVERY:
|
||||
case Auth::TOKEN_TYPE_INVITE:
|
||||
return Auth::SESSION_PROVIDER_EMAIL;
|
||||
case Auth::TOKEN_TYPE_MAGIC_URL:
|
||||
return Auth::SESSION_PROVIDER_MAGIC_URL;
|
||||
case Auth::TOKEN_TYPE_PHONE:
|
||||
return Auth::SESSION_PROVIDER_PHONE;
|
||||
case Auth::TOKEN_TYPE_OAUTH2:
|
||||
return Auth::SESSION_PROVIDER_OAUTH2;
|
||||
default:
|
||||
return SESSION_PROVIDER_TOKEN;
|
||||
return Auth::SESSION_PROVIDER_TOKEN;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode Session.
|
||||
*
|
||||
* @param string $session
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function decodeSession($session)
|
||||
{
|
||||
$session = \json_decode(\base64_decode($session), true);
|
||||
$default = ['id' => null, 'secret' => ''];
|
||||
|
||||
if (!\is_array($session)) {
|
||||
return $default;
|
||||
}
|
||||
|
||||
return \array_merge($default, $session);
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode.
|
||||
*
|
||||
* One-way encryption
|
||||
*
|
||||
* @deprecated We plan to deprecate this class in the future. Use Utopia Auth when possible.
|
||||
* @param $string
|
||||
*
|
||||
* @return string
|
||||
@@ -60,12 +206,124 @@ class Auth
|
||||
return \hash('sha256', $string);
|
||||
}
|
||||
|
||||
/**
|
||||
* Password Hash.
|
||||
*
|
||||
* One way string hashing for user passwords
|
||||
*
|
||||
* @param string $string
|
||||
* @param string $algo hashing algorithm to use
|
||||
* @param array $options algo-specific options
|
||||
*
|
||||
* @return bool|string|null
|
||||
*/
|
||||
public static function passwordHash(string $string, string $algo, array $options = [])
|
||||
{
|
||||
// Plain text not supported, just an alias. Switch to recommended algo
|
||||
if ($algo === 'plaintext') {
|
||||
$algo = Auth::DEFAULT_ALGO;
|
||||
$options = Auth::DEFAULT_ALGO_OPTIONS;
|
||||
}
|
||||
|
||||
if (!\in_array($algo, Auth::SUPPORTED_ALGOS)) {
|
||||
throw new \Exception('Hashing algorithm \'' . $algo . '\' is not supported.');
|
||||
}
|
||||
|
||||
switch ($algo) {
|
||||
case 'argon2':
|
||||
$hasher = new Argon2($options);
|
||||
return $hasher->hash($string);
|
||||
case 'bcrypt':
|
||||
$hasher = new Bcrypt($options);
|
||||
return $hasher->hash($string);
|
||||
case 'md5':
|
||||
$hasher = new Md5($options);
|
||||
return $hasher->hash($string);
|
||||
case 'sha':
|
||||
$hasher = new Sha($options);
|
||||
return $hasher->hash($string);
|
||||
case 'phpass':
|
||||
$hasher = new Phpass($options);
|
||||
return $hasher->hash($string);
|
||||
case 'scrypt':
|
||||
$hasher = new Scrypt($options);
|
||||
return $hasher->hash($string);
|
||||
case 'scryptMod':
|
||||
$hasher = new Scryptmodified($options);
|
||||
return $hasher->hash($string);
|
||||
default:
|
||||
throw new \Exception('Hashing algorithm \'' . $algo . '\' is not supported.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Password verify.
|
||||
*
|
||||
* @param string $plain
|
||||
* @param string $hash
|
||||
* @param string $algo hashing algorithm used to hash
|
||||
* @param array $options algo-specific options
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function passwordVerify(string $plain, string $hash, string $algo, array $options = [])
|
||||
{
|
||||
// Plain text not supported, just an alias. Switch to recommended algo
|
||||
if ($algo === 'plaintext') {
|
||||
$algo = Auth::DEFAULT_ALGO;
|
||||
$options = Auth::DEFAULT_ALGO_OPTIONS;
|
||||
}
|
||||
|
||||
if (!\in_array($algo, Auth::SUPPORTED_ALGOS)) {
|
||||
throw new \Exception('Hashing algorithm \'' . $algo . '\' is not supported.');
|
||||
}
|
||||
|
||||
switch ($algo) {
|
||||
case 'argon2':
|
||||
$hasher = new Argon2($options);
|
||||
return $hasher->verify($plain, $hash);
|
||||
case 'bcrypt':
|
||||
$hasher = new Bcrypt($options);
|
||||
return $hasher->verify($plain, $hash);
|
||||
case 'md5':
|
||||
$hasher = new Md5($options);
|
||||
return $hasher->verify($plain, $hash);
|
||||
case 'sha':
|
||||
$hasher = new Sha($options);
|
||||
return $hasher->verify($plain, $hash);
|
||||
case 'phpass':
|
||||
$hasher = new Phpass($options);
|
||||
return $hasher->verify($plain, $hash);
|
||||
case 'scrypt':
|
||||
$hasher = new Scrypt($options);
|
||||
return $hasher->verify($plain, $hash);
|
||||
case 'scryptMod':
|
||||
$hasher = new Scryptmodified($options);
|
||||
return $hasher->verify($plain, $hash);
|
||||
default:
|
||||
throw new \Exception('Hashing algorithm \'' . $algo . '\' is not supported.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Password Generator.
|
||||
*
|
||||
* Generate random password string
|
||||
*
|
||||
* @param int $length
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function passwordGenerator(int $length = 20): string
|
||||
{
|
||||
return \bin2hex(\random_bytes($length));
|
||||
}
|
||||
|
||||
/**
|
||||
* Token Generator.
|
||||
*
|
||||
* Generate random password string
|
||||
*
|
||||
* @deprecated We plan to deprecate this class in the future. Use Utopia Auth when possible.
|
||||
* @param int $length Length of returned token
|
||||
*
|
||||
* @return string
|
||||
@@ -82,17 +340,36 @@ class Auth
|
||||
return substr($token, 0, $length);
|
||||
}
|
||||
|
||||
/**
|
||||
* Code Generator.
|
||||
*
|
||||
* Generate random code string
|
||||
*
|
||||
* @param int $length
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function codeGenerator(int $length = 6): string
|
||||
{
|
||||
$value = '';
|
||||
|
||||
for ($i = 0; $i < $length; $i++) {
|
||||
$value .= random_int(0, 9);
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify token and check that its not expired.
|
||||
*
|
||||
* @deprecated We plan to deprecate this class in the future. Use Utopia Auth when possible.
|
||||
* @param array<Document> $tokens
|
||||
* @param int $type Type of token to verify, if null will verify any type
|
||||
* @param string $secret
|
||||
*
|
||||
* @return false|Document
|
||||
*/
|
||||
public static function tokenVerify(array $tokens, int $type = null, string $secret, Proof $proofForToken): false|Document
|
||||
public static function tokenVerify(array $tokens, int $type = null, string $secret): false|Document
|
||||
{
|
||||
foreach ($tokens as $token) {
|
||||
if (
|
||||
@@ -100,7 +377,7 @@ class Auth
|
||||
$token->isSet('expire') &&
|
||||
$token->isSet('type') &&
|
||||
($type === null || $token->getAttribute('type') === $type) &&
|
||||
$proofForToken->verify($secret, $token->getAttribute('secret')) &&
|
||||
$token->getAttribute('secret') === self::hash($secret) &&
|
||||
DateTime::formatTz($token->getAttribute('expire')) >= DateTime::formatTz(DateTime::now())
|
||||
) {
|
||||
return $token;
|
||||
@@ -113,19 +390,18 @@ class Auth
|
||||
/**
|
||||
* Verify session and check that its not expired.
|
||||
*
|
||||
* @deprecated We plan to deprecate this class in the future. Use Utopia Auth when possible.
|
||||
* @param array<Document> $sessions
|
||||
* @param string $secret
|
||||
*
|
||||
* @return bool|string
|
||||
*/
|
||||
public static function sessionVerify(array $sessions, string $secret, Token $proofForToken)
|
||||
public static function sessionVerify(array $sessions, string $secret)
|
||||
{
|
||||
foreach ($sessions as $session) {
|
||||
if (
|
||||
$session->isSet('secret') &&
|
||||
$session->isSet('provider') &&
|
||||
$proofForToken->verify($secret, $session->getAttribute('secret')) &&
|
||||
$session->getAttribute('secret') === self::hash($secret) &&
|
||||
DateTime::formatTz(DateTime::format(new \DateTime($session->getAttribute('expire')))) >= DateTime::formatTz(DateTime::now())
|
||||
) {
|
||||
return $session->getId();
|
||||
@@ -138,7 +414,6 @@ class Auth
|
||||
/**
|
||||
* Is Privileged User?
|
||||
*
|
||||
* @deprecated We plan to deprecate this class in the future. Use Utopia Auth when possible.
|
||||
* @param array<string> $roles
|
||||
*
|
||||
* @return bool
|
||||
@@ -146,9 +421,9 @@ class Auth
|
||||
public static function isPrivilegedUser(array $roles): bool
|
||||
{
|
||||
if (
|
||||
in_array(USER_ROLE_OWNER, $roles) ||
|
||||
in_array(USER_ROLE_DEVELOPER, $roles) ||
|
||||
in_array(USER_ROLE_ADMIN, $roles)
|
||||
in_array(self::USER_ROLE_OWNER, $roles) ||
|
||||
in_array(self::USER_ROLE_DEVELOPER, $roles) ||
|
||||
in_array(self::USER_ROLE_ADMIN, $roles)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
@@ -159,14 +434,13 @@ class Auth
|
||||
/**
|
||||
* Is App User?
|
||||
*
|
||||
* @deprecated We plan to deprecate this class in the future. Use Utopia Auth when possible.
|
||||
* @param array<string> $roles
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function isAppUser(array $roles): bool
|
||||
{
|
||||
if (in_array(USER_ROLE_APPS, $roles)) {
|
||||
if (in_array(self::USER_ROLE_APPS, $roles)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -176,7 +450,6 @@ class Auth
|
||||
/**
|
||||
* Returns all roles for a user.
|
||||
*
|
||||
* @deprecated We plan to deprecate this class in the future. Use Utopia Auth when possible.
|
||||
* @param Document $user
|
||||
* @return array<string>
|
||||
*/
|
||||
@@ -227,4 +500,16 @@ class Auth
|
||||
|
||||
return $roles;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if user is anonymous.
|
||||
*
|
||||
* @param Document $user
|
||||
* @return bool
|
||||
*/
|
||||
public static function isAnonymousUser(Document $user): bool
|
||||
{
|
||||
return is_null($user->getAttribute('email'))
|
||||
&& is_null($user->getAttribute('phone'));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Auth;
|
||||
|
||||
abstract class Hash
|
||||
{
|
||||
/**
|
||||
* @var array $options Hashing-algo specific options
|
||||
*/
|
||||
protected array $options = [];
|
||||
|
||||
/**
|
||||
* @param array $options Hashing-algo specific options
|
||||
*/
|
||||
public function __construct(array $options = [])
|
||||
{
|
||||
$this->setOptions($options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set hashing algo options
|
||||
*
|
||||
* @param array $options Hashing-algo specific options
|
||||
*/
|
||||
public function setOptions(array $options): self
|
||||
{
|
||||
$this->options = \array_merge([], $this->getDefaultOptions(), $options);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get hashing algo options
|
||||
*
|
||||
* @return array $options Hashing-algo specific options
|
||||
*/
|
||||
public function getOptions(): array
|
||||
{
|
||||
return $this->options;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $password Input password to hash
|
||||
*
|
||||
* @return string hash
|
||||
*/
|
||||
abstract public function hash(string $password): string;
|
||||
|
||||
/**
|
||||
* @param string $password Input password to validate
|
||||
* @param string $hash Hash to verify password against
|
||||
*
|
||||
* @return boolean true if password matches hash
|
||||
*/
|
||||
abstract public function verify(string $password, string $hash): bool;
|
||||
|
||||
/**
|
||||
* Get default options for specific hashing algo
|
||||
*
|
||||
* @return array options named array
|
||||
*/
|
||||
abstract public function getDefaultOptions(): array;
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Auth\Hash;
|
||||
|
||||
use Appwrite\Auth\Hash;
|
||||
|
||||
/*
|
||||
* Argon2 accepted options:
|
||||
* int threads
|
||||
* int time_cost
|
||||
* int memory_cost
|
||||
*
|
||||
* Reference: https://www.php.net/manual/en/function.password-hash.php#example-983
|
||||
*/
|
||||
class Argon2 extends Hash
|
||||
{
|
||||
/**
|
||||
* @param string $password Input password to hash
|
||||
*
|
||||
* @return string hash
|
||||
*/
|
||||
public function hash(string $password): string
|
||||
{
|
||||
return \password_hash($password, PASSWORD_ARGON2ID, $this->getOptions());
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $password Input password to validate
|
||||
* @param string $hash Hash to verify password against
|
||||
*
|
||||
* @return boolean true if password matches hash
|
||||
*/
|
||||
public function verify(string $password, string $hash): bool
|
||||
{
|
||||
return \password_verify($password, $hash);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get default options for specific hashing algo
|
||||
*
|
||||
* @return array options named array
|
||||
*/
|
||||
public function getDefaultOptions(): array
|
||||
{
|
||||
return ['memory_cost' => 65536, 'time_cost' => 4, 'threads' => 3];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Auth\Hash;
|
||||
|
||||
use Appwrite\Auth\Hash;
|
||||
|
||||
/*
|
||||
* Bcrypt accepted options:
|
||||
* int cost
|
||||
* string? salt; auto-generated if empty
|
||||
*
|
||||
* Reference: https://www.php.net/manual/en/password.constants.php
|
||||
*/
|
||||
class Bcrypt extends Hash
|
||||
{
|
||||
/**
|
||||
* @param string $password Input password to hash
|
||||
*
|
||||
* @return string hash
|
||||
*/
|
||||
public function hash(string $password): string
|
||||
{
|
||||
return \password_hash($password, PASSWORD_BCRYPT, $this->getOptions());
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $password Input password to validate
|
||||
* @param string $hash Hash to verify password against
|
||||
*
|
||||
* @return boolean true if password matches hash
|
||||
*/
|
||||
public function verify(string $password, string $hash): bool
|
||||
{
|
||||
return \password_verify($password, $hash);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get default options for specific hashing algo
|
||||
*
|
||||
* @return array options named array
|
||||
*/
|
||||
public function getDefaultOptions(): array
|
||||
{
|
||||
return [ 'cost' => 8 ];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Auth\Hash;
|
||||
|
||||
use Appwrite\Auth\Hash;
|
||||
|
||||
/*
|
||||
* MD5 does not accept any options.
|
||||
*
|
||||
* Reference: https://www.php.net/manual/en/function.md5.php
|
||||
*/
|
||||
class Md5 extends Hash
|
||||
{
|
||||
/**
|
||||
* @param string $password Input password to hash
|
||||
*
|
||||
* @return string hash
|
||||
*/
|
||||
public function hash(string $password): string
|
||||
{
|
||||
return \md5($password);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $password Input password to validate
|
||||
* @param string $hash Hash to verify password against
|
||||
*
|
||||
* @return boolean true if password matches hash
|
||||
*/
|
||||
public function verify(string $password, string $hash): bool
|
||||
{
|
||||
return $this->hash($password) === $hash;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get default options for specific hashing algo
|
||||
*
|
||||
* @return array options named array
|
||||
*/
|
||||
public function getDefaultOptions(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,290 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Portable PHP password hashing framework.
|
||||
* source Version 0.5 / genuine.
|
||||
* Written by Solar Designer <solar at openwall.com> in 2004-2017 and placed in
|
||||
* the public domain. Revised in subsequent years, still public domain.
|
||||
* There's absolutely no warranty.
|
||||
* The homepage URL for the source framework is: http://www.openwall.com/phpass/
|
||||
* Please be sure to update the Version line if you edit this file in any way.
|
||||
* It is suggested that you leave the main version number intact, but indicate
|
||||
* your project name (after the slash) and add your own revision information.
|
||||
* Please do not change the "private" password hashing method implemented in
|
||||
* here, thereby making your hashes incompatible. However, if you must, please
|
||||
* change the hash type identifier (the "$P$") to something different.
|
||||
* Obviously, since this code is in the public domain, the above are not
|
||||
* requirements (there can be none), but merely suggestions.
|
||||
*
|
||||
* @author Solar Designer <solar@openwall.com>
|
||||
* @copyright Copyright (C) 2017 All rights reserved.
|
||||
* @license http://www.opensource.org/licenses/mit-license.html MIT License; see LICENSE.txt
|
||||
*/
|
||||
|
||||
namespace Appwrite\Auth\Hash;
|
||||
|
||||
use Appwrite\Auth\Hash;
|
||||
|
||||
/*
|
||||
* PHPass accepted options:
|
||||
* int iteration_count_log2; The Logarithmic cost value used when generating hash values indicating the number of rounds used to generate hashes
|
||||
* string portable_hashes
|
||||
* string random_state; The cached random state
|
||||
*
|
||||
* Reference: https://github.com/photodude/phpass
|
||||
*/
|
||||
class Phpass extends Hash
|
||||
{
|
||||
/**
|
||||
* Alphabet used in itoa64 conversions.
|
||||
*
|
||||
* @var string
|
||||
* @since 0.1.0
|
||||
*/
|
||||
protected string $itoa64 = './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
|
||||
|
||||
/**
|
||||
* Get default options for specific hashing algo
|
||||
*
|
||||
* @return array options named array
|
||||
*/
|
||||
public function getDefaultOptions(): array
|
||||
{
|
||||
$randomState = \microtime();
|
||||
if (\function_exists('getmypid')) {
|
||||
$randomState .= getmypid();
|
||||
}
|
||||
|
||||
return ['iteration_count_log2' => 8, 'portable_hashes' => false, 'random_state' => $randomState];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $password Input password to hash
|
||||
*
|
||||
* @return string hash
|
||||
*/
|
||||
public function hash(string $password): string
|
||||
{
|
||||
$options = $this->getDefaultOptions();
|
||||
|
||||
$random = '';
|
||||
if (CRYPT_BLOWFISH === 1 && !$options['portable_hashes']) {
|
||||
$random = $this->getRandomBytes(16, $options);
|
||||
$hash = crypt($password, $this->gensaltBlowfish($random, $options));
|
||||
if (strlen($hash) === 60) {
|
||||
return $hash;
|
||||
}
|
||||
}
|
||||
if (strlen($random) < 6) {
|
||||
$random = $this->getRandomBytes(6, $options);
|
||||
}
|
||||
$hash = $this->cryptPrivate($password, $this->gensaltPrivate($random, $options));
|
||||
if (strlen($hash) === 34) {
|
||||
return $hash;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returning '*' on error is safe here, but would _not_ be safe
|
||||
* in a crypt(3)-like function used _both_ for generating new
|
||||
* hashes and for validating passwords against existing hashes.
|
||||
*/
|
||||
return '*';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $password Input password to validate
|
||||
* @param string $hash Hash to verify password against
|
||||
*
|
||||
* @return boolean true if password matches hash
|
||||
*/
|
||||
public function verify(string $password, string $hash): bool
|
||||
{
|
||||
$verificationHash = $this->cryptPrivate($password, $hash);
|
||||
if ($verificationHash[0] === '*') {
|
||||
$verificationHash = crypt($password, $hash);
|
||||
}
|
||||
|
||||
/**
|
||||
* This is not constant-time. In order to keep the code simple,
|
||||
* for timing safety we currently rely on the salts being
|
||||
* unpredictable, which they are at least in the non-fallback
|
||||
* cases (that is, when we use /dev/urandom and bcrypt).
|
||||
*/
|
||||
return $hash === $verificationHash;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $count
|
||||
*
|
||||
* @return String $output
|
||||
* @since 0.1.0
|
||||
* @throws Exception Thows an Exception if the $count parameter is not a positive integer.
|
||||
*/
|
||||
protected function getRandomBytes(int $count, array $options): string
|
||||
{
|
||||
if (!is_int($count) || $count < 1) {
|
||||
throw new \Exception('Argument count must be a positive integer');
|
||||
}
|
||||
$output = '';
|
||||
if (@is_readable('/dev/urandom') && ($fh = @fopen('/dev/urandom', 'rb'))) {
|
||||
$output = fread($fh, $count);
|
||||
fclose($fh);
|
||||
}
|
||||
|
||||
if (strlen($output) < $count) {
|
||||
$output = '';
|
||||
|
||||
for ($i = 0; $i < $count; $i += 16) {
|
||||
$options['iteration_count_log2'] = md5(microtime() . $options['iteration_count_log2']);
|
||||
$output .= md5($options['iteration_count_log2'], true);
|
||||
}
|
||||
|
||||
$output = substr($output, 0, $count);
|
||||
}
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param String $input
|
||||
* @param int $count
|
||||
*
|
||||
* @return String $output
|
||||
* @since 0.1.0
|
||||
* @throws Exception Thows an Exception if the $count parameter is not a positive integer.
|
||||
*/
|
||||
protected function encode64($input, $count)
|
||||
{
|
||||
if (!is_int($count) || $count < 1) {
|
||||
throw new \Exception('Argument count must be a positive integer');
|
||||
}
|
||||
$output = '';
|
||||
$i = 0;
|
||||
do {
|
||||
$value = ord($input[$i++]);
|
||||
$output .= $this->itoa64[$value & 0x3f];
|
||||
if ($i < $count) {
|
||||
$value |= ord($input[$i]) << 8;
|
||||
}
|
||||
$output .= $this->itoa64[($value >> 6) & 0x3f];
|
||||
if ($i++ >= $count) {
|
||||
break;
|
||||
}
|
||||
if ($i < $count) {
|
||||
$value |= ord($input[$i]) << 16;
|
||||
}
|
||||
$output .= $this->itoa64[($value >> 12) & 0x3f];
|
||||
if ($i++ >= $count) {
|
||||
break;
|
||||
}
|
||||
$output .= $this->itoa64[($value >> 18) & 0x3f];
|
||||
} while ($i < $count);
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param String $input
|
||||
*
|
||||
* @return String $output
|
||||
* @since 0.1.0
|
||||
*/
|
||||
private function gensaltPrivate($input, $options)
|
||||
{
|
||||
$output = '$P$';
|
||||
$output .= $this->itoa64[min($options['iteration_count_log2'] + ((PHP_VERSION >= '5') ? 5 : 3), 30)];
|
||||
$output .= $this->encode64($input, 6);
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param String $password
|
||||
* @param String $setting
|
||||
*
|
||||
* @return String $output
|
||||
* @since 0.1.0
|
||||
*/
|
||||
private function cryptPrivate($password, $setting)
|
||||
{
|
||||
$output = '*0';
|
||||
if (substr($setting, 0, 2) === $output) {
|
||||
$output = '*1';
|
||||
}
|
||||
$id = substr($setting, 0, 3);
|
||||
// We use "$P$", phpBB3 uses "$H$" for the same thing
|
||||
if ($id !== '$P$' && $id !== '$H$') {
|
||||
return $output;
|
||||
}
|
||||
$count_log2 = strpos($this->itoa64, $setting[3]);
|
||||
if ($count_log2 < 7 || $count_log2 > 30) {
|
||||
return $output;
|
||||
}
|
||||
$count = 1 << $count_log2;
|
||||
$salt = substr($setting, 4, 8);
|
||||
if (strlen($salt) !== 8) {
|
||||
return $output;
|
||||
}
|
||||
/**
|
||||
* We were kind of forced to use MD5 here since it's the only
|
||||
* cryptographic primitive that was available in all versions of PHP
|
||||
* in use. To implement our own low-level crypto in PHP
|
||||
* would have result in much worse performance and
|
||||
* consequently in lower iteration counts and hashes that are
|
||||
* quicker to crack (by non-PHP code).
|
||||
*/
|
||||
$hash = md5($salt . $password, true);
|
||||
do {
|
||||
$hash = md5($hash . $password, true);
|
||||
} while (--$count);
|
||||
$output = substr($setting, 0, 12);
|
||||
$output .= $this->encode64($hash, 16);
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param String $input
|
||||
*
|
||||
* @return String $output
|
||||
* @since 0.1.0
|
||||
*/
|
||||
private function gensaltBlowfish($input, $options)
|
||||
{
|
||||
/**
|
||||
* This one needs to use a different order of characters and a
|
||||
* different encoding scheme from the one in encode64() above.
|
||||
* We care because the last character in our encoded string will
|
||||
* only represent 2 bits. While two known implementations of
|
||||
* bcrypt will happily accept and correct a salt string which
|
||||
* has the 4 unused bits set to non-zero, we do not want to take
|
||||
* chances and we also do not want to waste an additional byte
|
||||
* of entropy.
|
||||
*/
|
||||
$itoa64 = './ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
||||
$output = '$2a$';
|
||||
$output .= chr(ord('0') + intval($options['iteration_count_log2'] / 10));
|
||||
$output .= chr(ord('0') + $options['iteration_count_log2'] % 10);
|
||||
$output .= '$';
|
||||
$i = 0;
|
||||
do {
|
||||
$c1 = ord($input[$i++]);
|
||||
$output .= $itoa64[$c1 >> 2];
|
||||
$c1 = ($c1 & 0x03) << 4;
|
||||
if ($i >= 16) {
|
||||
$output .= $itoa64[$c1];
|
||||
break;
|
||||
}
|
||||
$c2 = ord($input[$i++]);
|
||||
$c1 |= $c2 >> 4;
|
||||
$output .= $itoa64[$c1];
|
||||
$c1 = ($c2 & 0x0f) << 2;
|
||||
$c2 = ord($input[$i++]);
|
||||
$c1 |= $c2 >> 6;
|
||||
$output .= $itoa64[$c1];
|
||||
$output .= $itoa64[$c2 & 0x3f];
|
||||
} while (1);
|
||||
|
||||
return $output;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Auth\Hash;
|
||||
|
||||
use Appwrite\Auth\Hash;
|
||||
|
||||
/*
|
||||
* Scrypt accepted options:
|
||||
* string? salt; auto-generated if empty
|
||||
* int costCpu
|
||||
* int costMemory
|
||||
* int costParallel
|
||||
* int length
|
||||
*
|
||||
* Reference: https://github.com/DomBlack/php-scrypt/blob/master/scrypt.php#L112-L116
|
||||
*/
|
||||
class Scrypt extends Hash
|
||||
{
|
||||
/**
|
||||
* @param string $password Input password to hash
|
||||
*
|
||||
* @return string hash
|
||||
*/
|
||||
public function hash(string $password): string
|
||||
{
|
||||
$options = $this->getOptions();
|
||||
|
||||
return \scrypt($password, $options['salt'], $options['costCpu'], $options['costMemory'], $options['costParallel'], $options['length']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $password Input password to validate
|
||||
* @param string $hash Hash to verify password against
|
||||
*
|
||||
* @return boolean true if password matches hash
|
||||
*/
|
||||
public function verify(string $password, string $hash): bool
|
||||
{
|
||||
return $hash === $this->hash($password);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get default options for specific hashing algo
|
||||
*
|
||||
* @return array options named array
|
||||
*/
|
||||
public function getDefaultOptions(): array
|
||||
{
|
||||
return [ 'costCpu' => 8, 'costMemory' => 14, 'costParallel' => 1, 'length' => 64 ];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Auth\Hash;
|
||||
|
||||
use Appwrite\Auth\Hash;
|
||||
|
||||
/*
|
||||
* This is Scrypt hash with some additional steps added by Google.
|
||||
*
|
||||
* string salt
|
||||
* string saltSeparator
|
||||
* strin signerKey
|
||||
*
|
||||
* Reference: https://github.com/DomBlack/php-scrypt/blob/master/scrypt.php#L112-L116
|
||||
*/
|
||||
class Scryptmodified extends Hash
|
||||
{
|
||||
/**
|
||||
* @param string $password Input password to hash
|
||||
*
|
||||
* @return string hash
|
||||
*/
|
||||
public function hash(string $password): string
|
||||
{
|
||||
$options = $this->getOptions();
|
||||
|
||||
$derivedKeyBytes = $this->generateDerivedKey($password);
|
||||
$signerKeyBytes = \base64_decode($options['signerKey']);
|
||||
|
||||
$hashedPassword = $this->hashKeys($signerKeyBytes, $derivedKeyBytes);
|
||||
|
||||
return \base64_encode($hashedPassword);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $password Input password to validate
|
||||
* @param string $hash Hash to verify password against
|
||||
*
|
||||
* @return boolean true if password matches hash
|
||||
*/
|
||||
public function verify(string $password, string $hash): bool
|
||||
{
|
||||
return $this->hash($password) === $hash;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get default options for specific hashing algo
|
||||
*
|
||||
* @return array options named array
|
||||
*/
|
||||
public function getDefaultOptions(): array
|
||||
{
|
||||
return [ ];
|
||||
}
|
||||
|
||||
private function generateDerivedKey(string $password)
|
||||
{
|
||||
$options = $this->getOptions();
|
||||
|
||||
$saltBytes = \base64_decode($options['salt']);
|
||||
$saltSeparatorBytes = \base64_decode($options['saltSeparator']);
|
||||
|
||||
$password = mb_convert_encoding($password, 'UTF-8');
|
||||
$derivedKey = \scrypt($password, $saltBytes . $saltSeparatorBytes, 16384, 8, 1, 64);
|
||||
$derivedKey = \hex2bin($derivedKey);
|
||||
|
||||
return $derivedKey;
|
||||
}
|
||||
|
||||
private function hashKeys($signerKeyBytes, $derivedKeyBytes): string
|
||||
{
|
||||
$key = \substr($derivedKeyBytes, 0, 32);
|
||||
|
||||
$iv = "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00";
|
||||
|
||||
$hash = \openssl_encrypt($signerKeyBytes, 'aes-256-ctr', $key, OPENSSL_RAW_DATA, $iv);
|
||||
|
||||
return $hash;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Auth\Hash;
|
||||
|
||||
use Appwrite\Auth\Hash;
|
||||
|
||||
/*
|
||||
* SHA accepted options:
|
||||
* string? version. Allowed:
|
||||
* - Version 1: sha1
|
||||
* - Version 2: sha224, sha256, sha384, sha512/224, sha512/256, sha512
|
||||
* - Version 3: sha3-224, sha3-256, sha3-384, sha3-512
|
||||
*
|
||||
* Reference: https://www.php.net/manual/en/function.hash-algos.php
|
||||
*/
|
||||
class Sha extends Hash
|
||||
{
|
||||
/**
|
||||
* @param string $password Input password to hash
|
||||
*
|
||||
* @return string hash
|
||||
*/
|
||||
public function hash(string $password): string
|
||||
{
|
||||
$algo = $this->getOptions()['version'];
|
||||
|
||||
return \hash($algo, $password);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $password Input password to validate
|
||||
* @param string $hash Hash to verify password against
|
||||
*
|
||||
* @return boolean true if password matches hash
|
||||
*/
|
||||
public function verify(string $password, string $hash): bool
|
||||
{
|
||||
return $this->hash($password) === $hash;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get default options for specific hashing algo
|
||||
*
|
||||
* @return array options named array
|
||||
*/
|
||||
public function getDefaultOptions(): array
|
||||
{
|
||||
return [ 'version' => 'sha3-512' ];
|
||||
}
|
||||
}
|
||||
@@ -110,16 +110,16 @@ class Key
|
||||
$secret = $key;
|
||||
}
|
||||
|
||||
$role = USER_ROLE_APPS;
|
||||
$role = Auth::USER_ROLE_APPS;
|
||||
$roles = Config::getParam('roles', []);
|
||||
$scopes = $roles[USER_ROLE_APPS]['scopes'] ?? [];
|
||||
$scopes = $roles[Auth::USER_ROLE_APPS]['scopes'] ?? [];
|
||||
$expired = false;
|
||||
|
||||
$guestKey = new Key(
|
||||
$project->getId(),
|
||||
$type,
|
||||
USER_ROLE_GUESTS,
|
||||
$roles[USER_ROLE_GUESTS]['scopes'] ?? [],
|
||||
Auth::USER_ROLE_GUESTS,
|
||||
$roles[Auth::USER_ROLE_GUESTS]['scopes'] ?? [],
|
||||
'UNKNOWN'
|
||||
);
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
|
||||
namespace Appwrite\Auth\MFA;
|
||||
|
||||
use Appwrite\Auth\Auth;
|
||||
use OTPHP\OTP;
|
||||
use Utopia\Auth\Proofs\Token;
|
||||
|
||||
abstract class Type
|
||||
{
|
||||
@@ -51,10 +51,9 @@ abstract class Type
|
||||
public static function generateBackupCodes(int $length = 10, int $total = 6): array
|
||||
{
|
||||
$backups = [];
|
||||
$token = new Token($length);
|
||||
|
||||
for ($i = 0; $i < $total; $i++) {
|
||||
$backups[] = $token->generate();
|
||||
$backups[] = Auth::tokenGenerator($length);
|
||||
}
|
||||
|
||||
return $backups;
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
namespace Appwrite\Auth\Validator;
|
||||
|
||||
use Utopia\Auth\Hash;
|
||||
use Appwrite\Auth\Auth;
|
||||
|
||||
/**
|
||||
* Password.
|
||||
@@ -12,14 +12,16 @@ use Utopia\Auth\Hash;
|
||||
class PasswordHistory extends Password
|
||||
{
|
||||
protected array $history;
|
||||
protected Hash $hash;
|
||||
protected string $algo;
|
||||
protected array $algoOptions;
|
||||
|
||||
public function __construct(array $history, Hash $hash)
|
||||
public function __construct(array $history, string $algo, array $algoOptions = [])
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->history = $history;
|
||||
$this->hash = $hash;
|
||||
$this->algo = $algo;
|
||||
$this->algoOptions = $algoOptions;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -44,7 +46,7 @@ class PasswordHistory extends Password
|
||||
public function isValid($value): bool
|
||||
{
|
||||
foreach ($this->history as $hash) {
|
||||
if (!empty($hash) && $this->hash->verify($value, $hash)) {
|
||||
if (!empty($hash) && Auth::passwordVerify($value, $hash, $this->algo, $this->algoOptions)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,7 +89,7 @@ abstract class Migration
|
||||
'1.7.2' => 'V22',
|
||||
'1.7.3' => 'V22',
|
||||
'1.7.4' => 'V22',
|
||||
'1.8.0' => 'V23'
|
||||
'1.8.0' => 'V23',
|
||||
];
|
||||
|
||||
/**
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace Appwrite\Migration\Version;
|
||||
|
||||
use Appwrite\Auth\Auth;
|
||||
use Appwrite\Migration\Migration;
|
||||
use Utopia\CLI\Console;
|
||||
use Utopia\Config\Config;
|
||||
@@ -117,7 +118,7 @@ class V16 extends Migration
|
||||
* Set default authDuration
|
||||
*/
|
||||
$document->setAttribute('auths', array_merge($document->getAttribute('auths', []), [
|
||||
'duration' => TOKEN_EXPIRATION_LOGIN_LONG
|
||||
'duration' => Auth::TOKEN_EXPIRATION_LOGIN_LONG
|
||||
]));
|
||||
|
||||
/**
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
|
||||
namespace Appwrite\Migration\Version;
|
||||
|
||||
use Appwrite\Auth\Auth;
|
||||
use Appwrite\Migration\Migration;
|
||||
use Utopia\Auth\Proofs\Password;
|
||||
use Utopia\CLI\Console;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
@@ -270,7 +270,7 @@ class V17 extends Migration
|
||||
* Set hashOptions type
|
||||
*/
|
||||
$document->setAttribute('hashOptions', array_merge($document->getAttribute('hashOptions', []), [
|
||||
'type' => $document->getAttribute('hash', (new Password())->getHash()->getName())
|
||||
'type' => $document->getAttribute('hash', Auth::DEFAULT_ALGO)
|
||||
]));
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace Appwrite\Migration\Version;
|
||||
|
||||
use Appwrite\Auth\Auth;
|
||||
use Appwrite\Migration\Migration;
|
||||
use Exception;
|
||||
use PDOException;
|
||||
@@ -631,15 +632,15 @@ class V20 extends Migration
|
||||
}
|
||||
break;
|
||||
case 'sessions':
|
||||
$duration = $this->project->getAttribute('auths', [])['duration'] ?? TOKEN_EXPIRATION_LOGIN_LONG;
|
||||
$duration = $this->project->getAttribute('auths', [])['duration'] ?? Auth::TOKEN_EXPIRATION_LOGIN_LONG;
|
||||
$expire = DateTime::addSeconds(new \DateTime(), $duration);
|
||||
$document->setAttribute('expire', $expire);
|
||||
|
||||
$factors = match ($document->getAttribute('provider')) {
|
||||
SESSION_PROVIDER_EMAIL => ['password'],
|
||||
SESSION_PROVIDER_PHONE => ['phone'],
|
||||
SESSION_PROVIDER_ANONYMOUS => ['anonymous'],
|
||||
SESSION_PROVIDER_TOKEN => ['token'],
|
||||
Auth::SESSION_PROVIDER_EMAIL => ['password'],
|
||||
Auth::SESSION_PROVIDER_PHONE => ['phone'],
|
||||
Auth::SESSION_PROVIDER_ANONYMOUS => ['anonymous'],
|
||||
Auth::SESSION_PROVIDER_TOKEN => ['token'],
|
||||
default => ['email'],
|
||||
};
|
||||
|
||||
|
||||
@@ -8,6 +8,9 @@ use Throwable;
|
||||
use Utopia\CLI\Console;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Exception\Conflict;
|
||||
use Utopia\Database\Exception\Structure;
|
||||
use Utopia\Database\Exception\Timeout;
|
||||
|
||||
class V23 extends Migration
|
||||
{
|
||||
@@ -19,7 +22,24 @@ class V23 extends Migration
|
||||
/**
|
||||
* Disable SubQueries for Performance.
|
||||
*/
|
||||
foreach (['subQueryIndexes', 'subQueryPlatforms', 'subQueryDomains', 'subQueryKeys', 'subQueryDevKeys', 'subQueryWebhooks', 'subQuerySessions', 'subQueryTokens', 'subQueryMemberships', 'subQueryVariables', 'subQueryChallenges', 'subQueryProjectVariables', 'subQueryTargets', 'subQueryTopicTargets'] as $name) {
|
||||
$subQueries = [
|
||||
'subQueryAttributes',
|
||||
'subQueryAuthenticators',
|
||||
'subQueryChallenges',
|
||||
'subQueryDevKeys',
|
||||
'subQueryIndexes',
|
||||
'subQueryKeys',
|
||||
'subQueryMemberships',
|
||||
'subQueryPlatforms',
|
||||
'subQueryProjectVariables',
|
||||
'subQuerySessions',
|
||||
'subQueryTargets',
|
||||
'subQueryTokens',
|
||||
'subQueryTopicTargets',
|
||||
'subQueryVariables',
|
||||
'subQueryWebhooks',
|
||||
];
|
||||
foreach ($subQueries as $name) {
|
||||
Database::addFilter(
|
||||
$name,
|
||||
fn () => null,
|
||||
@@ -27,26 +47,158 @@ class V23 extends Migration
|
||||
);
|
||||
}
|
||||
|
||||
Console::info('Migrating databases');
|
||||
$this->migrateDatabases();
|
||||
Console::info('Migrating collections');
|
||||
$this->migrateCollections();
|
||||
|
||||
if ($this->project->getSequence() != 'console') {
|
||||
Console::info('Migrating Databases');
|
||||
$this->migrateDatabases();
|
||||
}
|
||||
|
||||
Console::info('Migrating Buckets');
|
||||
$this->migrateBuckets();
|
||||
|
||||
Console::info('Migrating documents');
|
||||
$this->forEachDocument($this->migrateDocument(...));
|
||||
}
|
||||
|
||||
/**
|
||||
* Migrate Databases.
|
||||
* Migrate Collections.
|
||||
*
|
||||
* @return void
|
||||
* @throws Exception|Throwable
|
||||
*/
|
||||
private function migrateDatabases(): void
|
||||
private function migrateCollections(): void
|
||||
{
|
||||
if ($this->project->getId() === 'console') {
|
||||
return;
|
||||
$projectInternalId = $this->project->getSequence();
|
||||
|
||||
if (empty($projectInternalId)) {
|
||||
throw new Exception('Project ID is null');
|
||||
}
|
||||
|
||||
// since required + default can't be used together
|
||||
// so first creating the attribute then bulk updating the attribute
|
||||
$this->createAttributeFromCollection($this->dbForProject, 'databases', 'type');
|
||||
$this->dbForProject->updateDocuments('databases', new Document(['type' => 'legacy']));
|
||||
$collectionType = match ($projectInternalId) {
|
||||
'console' => 'console',
|
||||
default => 'projects',
|
||||
};
|
||||
|
||||
$collections = $this->collections[$collectionType];
|
||||
|
||||
foreach ($collections as $collection) {
|
||||
$id = $collection['$id'];
|
||||
|
||||
if (empty($id)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Console::log("Migrating collection \"{$id}\"");
|
||||
|
||||
// Clear cache to ensure new $sequence is used
|
||||
$this->dbForProject->purgeCachedCollection($id);
|
||||
$this->dbForProject->purgeCachedDocument(Database::METADATA, $id);
|
||||
|
||||
switch ($id) {
|
||||
case '_metadata':
|
||||
$this->createCollection('transactions');
|
||||
$this->createCollection('transactionLogs');
|
||||
break;
|
||||
case 'projects':
|
||||
$attributes = [
|
||||
'pingCount',
|
||||
'pingedAt'
|
||||
];
|
||||
try {
|
||||
$this->createAttributesFromCollection($this->dbForProject, $id, $attributes);
|
||||
} catch (\Throwable $th) {
|
||||
Console::warning('Failed to create attributes "' . \implode(', ', $attributes) . "\" in collection {$id}: {$th->getMessage()}");
|
||||
}
|
||||
$this->dbForProject->purgeCachedCollection($id);
|
||||
break;
|
||||
case 'databases':
|
||||
$attributes = [
|
||||
'type',
|
||||
];
|
||||
try {
|
||||
$this->createAttributesFromCollection($this->dbForProject, $id, $attributes);
|
||||
} catch (\Throwable $th) {
|
||||
Console::warning('Failed to create attributes "' . \implode(', ', $attributes) . "\" in collection {$id}: {$th->getMessage()}");
|
||||
}
|
||||
$this->dbForProject->purgeCachedCollection($id);
|
||||
break;
|
||||
case 'schedules':
|
||||
try {
|
||||
$this->dbForProject->updateAttribute($id, 'resourceInternalId', required: false);
|
||||
} catch (Throwable $th) {
|
||||
Console::warning("'resourceInternalId' from {$id}: {$th->getMessage()}");
|
||||
}
|
||||
$this->dbForProject->purgeCachedCollection($id);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Migrate all Database Table tables
|
||||
*
|
||||
* @return void
|
||||
* @throws Exception
|
||||
*/
|
||||
private function migrateDatabases(): void
|
||||
{
|
||||
$this->dbForProject->foreach('databases', function (Document $database) {
|
||||
Console::log("Migrating Collections of {$database->getId()} ({$database->getAttribute('name')})");
|
||||
|
||||
$databaseTable = "database_{$database->getSequence()}";
|
||||
$this->dbForProject->purgeCachedCollection($databaseTable);
|
||||
|
||||
$this->dbForProject->foreach($databaseTable, function (Document $collection) use ($databaseTable) {
|
||||
Console::log("Migrating Collection of {$collection->getId()} ({$collection->getAttribute('name')})");
|
||||
|
||||
$collectionTable = "{$databaseTable}_collection_{$collection->getSequence()}";
|
||||
$this->dbForProject->purgeCachedCollection($collectionTable);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Migrate all Bucket tables
|
||||
*
|
||||
* @return void
|
||||
* @throws \Exception
|
||||
* @throws \PDOException
|
||||
*/
|
||||
protected function migrateBuckets(): void
|
||||
{
|
||||
$this->dbForProject->foreach('buckets', function (Document $bucket) {
|
||||
Console::log("Migrating Bucket {$bucket->getId()} ({$bucket->getAttribute('name')})");
|
||||
|
||||
$bucketTable = "bucket_{$bucket->getSequence()}";
|
||||
$this->dbForProject->purgeCachedCollection($bucketTable);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Fix run on each document
|
||||
*
|
||||
* @param Document $document
|
||||
* @return Document
|
||||
* @throws Conflict
|
||||
* @throws Structure
|
||||
* @throws Timeout
|
||||
* @throws \Utopia\Database\Exception
|
||||
* @throws \Utopia\Database\Exception\Authorization
|
||||
* @throws \Utopia\Database\Exception\Query
|
||||
*/
|
||||
private function migrateDocument(Document $document): Document
|
||||
{
|
||||
switch ($document->getCollection()) {
|
||||
case 'databases':
|
||||
$document->setAttribute('type', $document->getAttribute('type', 'legacy'));
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return $document;
|
||||
}
|
||||
}
|
||||
|
||||
+12
-15
@@ -160,7 +160,7 @@ class Create extends Action
|
||||
throw new Exception($this->getParentInvalidTypeException(), "Cannot create an index for a relationship $contextType: " . $oldAttributes[$attributeIndex]['key']);
|
||||
}
|
||||
|
||||
// ensure attribute is available
|
||||
// Ensure attribute is available
|
||||
if ($attributeStatus !== 'available') {
|
||||
$contextType = ucfirst($contextType);
|
||||
throw new Exception($this->getParentNotAvailableException(), "$contextType not available: " . $oldAttributes[$attributeIndex]['key']);
|
||||
@@ -171,7 +171,7 @@ class Create extends Action
|
||||
}
|
||||
|
||||
if ($attributeArray === true) {
|
||||
$lengths[$i] = Database::ARRAY_INDEX_LENGTH;
|
||||
$lengths[$i] = Database::MAX_ARRAY_INDEX_LENGTH;
|
||||
$orders[$i] = null;
|
||||
}
|
||||
}
|
||||
@@ -190,21 +190,18 @@ class Create extends Action
|
||||
'orders' => $orders,
|
||||
]);
|
||||
|
||||
$maxIndexLength = $dbForProject->getAdapter()->getMaxIndexLength();
|
||||
$internalIndexesKeys = $dbForProject->getAdapter()->getInternalIndexesKeys();
|
||||
$supportForIndexArray = $dbForProject->getAdapter()->getSupportForIndexArray();
|
||||
$supportForSpatialAttributes = $dbForProject->getAdapter()->getSupportForSpatialAttributes();
|
||||
$supportForSpatialIndexNull = $dbForProject->getAdapter()->getSupportForSpatialIndexNull();
|
||||
$supportForSpatialIndexOrder = $dbForProject->getAdapter()->getSupportForSpatialIndexOrder();
|
||||
|
||||
$validator = new IndexValidator(
|
||||
$collection->getAttribute('attributes'),
|
||||
$maxIndexLength,
|
||||
$internalIndexesKeys,
|
||||
$supportForIndexArray,
|
||||
$supportForSpatialAttributes,
|
||||
$supportForSpatialIndexNull,
|
||||
$supportForSpatialIndexOrder
|
||||
$collection->getAttribute('indexes'),
|
||||
$dbForProject->getAdapter()->getMaxIndexLength(),
|
||||
$dbForProject->getAdapter()->getInternalIndexesKeys(),
|
||||
$dbForProject->getAdapter()->getSupportForIndexArray(),
|
||||
$dbForProject->getAdapter()->getSupportForSpatialIndexNull(),
|
||||
$dbForProject->getAdapter()->getSupportForSpatialIndexOrder(),
|
||||
$dbForProject->getAdapter()->getSupportForVectors(),
|
||||
$dbForProject->getAdapter()->getSupportForAttributes(),
|
||||
$dbForProject->getAdapter()->getSupportForMultipleFulltextIndexes(),
|
||||
$dbForProject->getAdapter()->getSupportForIdenticalIndexes()
|
||||
);
|
||||
|
||||
if (!$validator->isValid($index)) {
|
||||
|
||||
@@ -18,8 +18,6 @@ use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Executor\Executor;
|
||||
use MaxMind\Db\Reader;
|
||||
use Utopia\Auth\Proofs\Token;
|
||||
use Utopia\Auth\Store;
|
||||
use Utopia\CLI\Console;
|
||||
use Utopia\Config\Config;
|
||||
use Utopia\Database\Database;
|
||||
@@ -94,8 +92,6 @@ class Create extends Base
|
||||
->inject('queueForStatsUsage')
|
||||
->inject('queueForFunctions')
|
||||
->inject('geodb')
|
||||
->inject('store')
|
||||
->inject('proofForToken')
|
||||
->inject('executor')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
@@ -118,8 +114,6 @@ class Create extends Base
|
||||
StatsUsage $queueForStatsUsage,
|
||||
Func $queueForFunctions,
|
||||
Reader $geodb,
|
||||
Store $store,
|
||||
Token $proofForToken,
|
||||
Executor $executor
|
||||
) {
|
||||
$async = \strval($async) === 'true' || \strval($async) === '1';
|
||||
@@ -204,7 +198,7 @@ class Create extends Base
|
||||
|
||||
foreach ($sessions as $session) {
|
||||
/** @var Utopia\Database\Document $session */
|
||||
if ($proofForToken->verify($store->getProperty('secret', ''), $session->getAttribute('secret'))) { // Find most recent active session for user ID and JWT headers
|
||||
if ($session->getAttribute('secret') == Auth::hash(Auth::$secret)) { // If current session delete the cookies too
|
||||
$current = $session;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,11 +2,10 @@
|
||||
|
||||
namespace Appwrite\Platform\Tasks;
|
||||
|
||||
use Appwrite\Auth\Auth;
|
||||
use Appwrite\Docker\Compose;
|
||||
use Appwrite\Docker\Env;
|
||||
use Appwrite\Utopia\View;
|
||||
use Utopia\Auth\Proofs\Password;
|
||||
use Utopia\Auth\Proofs\Token;
|
||||
use Utopia\CLI\Console;
|
||||
use Utopia\Config\Config;
|
||||
use Utopia\Platform\Action;
|
||||
@@ -150,8 +149,6 @@ class Install extends Action
|
||||
|
||||
$input = [];
|
||||
|
||||
$password = new Password();
|
||||
$token = new Token();
|
||||
foreach ($vars as $var) {
|
||||
if (!empty($var['filter']) && ($interactive !== 'Y' || !Console::isInteractive())) {
|
||||
if ($data && $var['default'] !== null) {
|
||||
@@ -160,12 +157,12 @@ class Install extends Action
|
||||
}
|
||||
|
||||
if ($var['filter'] === 'token') {
|
||||
$input[$var['name']] = $token->generate();
|
||||
$input[$var['name']] = Auth::tokenGenerator();
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($var['filter'] === 'password') {
|
||||
$input[$var['name']] = $password->generate();
|
||||
$input[$var['name']] = Auth::passwordGenerator();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace Appwrite\Platform\Workers;
|
||||
|
||||
use Appwrite\Auth\Auth;
|
||||
use Exception;
|
||||
use Throwable;
|
||||
use Utopia\Audit\Audit;
|
||||
@@ -84,7 +85,7 @@ class Audits extends Action
|
||||
|
||||
$userName = $user->getAttribute('name', '');
|
||||
$userEmail = $user->getAttribute('email', '');
|
||||
$userType = $user->getAttribute('type', ACTIVITY_TYPE_USER);
|
||||
$userType = $user->getAttribute('type', Auth::ACTIVITY_TYPE_USER);
|
||||
|
||||
// Create event data
|
||||
$eventData = [
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace Appwrite\Platform\Workers;
|
||||
|
||||
use Appwrite\Auth\Auth;
|
||||
use Appwrite\Certificates\Adapter as CertificatesAdapter;
|
||||
use Appwrite\Deletes\Identities;
|
||||
use Appwrite\Deletes\Targets;
|
||||
@@ -707,7 +708,7 @@ class Deletes extends Action
|
||||
private function deleteExpiredSessions(Document $project, callable $getProjectDB): void
|
||||
{
|
||||
$dbForProject = $getProjectDB($project);
|
||||
$duration = $project->getAttribute('auths', [])['duration'] ?? TOKEN_EXPIRATION_LOGIN_LONG;
|
||||
$duration = $project->getAttribute('auths', [])['duration'] ?? Auth::TOKEN_EXPIRATION_LOGIN_LONG;
|
||||
$expired = DateTime::addSeconds(new \DateTime(), -1 * $duration);
|
||||
|
||||
// Delete Sessions
|
||||
|
||||
@@ -22,6 +22,12 @@ class ConsoleVariables extends Model
|
||||
'default' => '',
|
||||
'example' => '127.0.0.1',
|
||||
])
|
||||
->addRule('_APP_COMPUTE_BUILD_TIMEOUT', [
|
||||
'type' => self::TYPE_INTEGER,
|
||||
'description' => 'Maximum build timeout in seconds.',
|
||||
'default' => '',
|
||||
'example' => 900,
|
||||
])
|
||||
->addRule('_APP_DOMAIN_TARGET_AAAA', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'AAAA target for your Appwrite custom domains.',
|
||||
|
||||
@@ -105,7 +105,7 @@ class Project extends Model
|
||||
->addRule('authDuration', [
|
||||
'type' => self::TYPE_INTEGER,
|
||||
'description' => 'Session duration in seconds.',
|
||||
'default' => TOKEN_EXPIRATION_LOGIN_LONG,
|
||||
'default' => Auth::TOKEN_EXPIRATION_LOGIN_LONG,
|
||||
'example' => 60,
|
||||
])
|
||||
->addRule('authLimit', [
|
||||
@@ -372,7 +372,7 @@ class Project extends Model
|
||||
$auth = Config::getParam('auth', []);
|
||||
|
||||
$document->setAttribute('authLimit', $authValues['limit'] ?? 0);
|
||||
$document->setAttribute('authDuration', $authValues['duration'] ?? TOKEN_EXPIRATION_LOGIN_LONG);
|
||||
$document->setAttribute('authDuration', $authValues['duration'] ?? Auth::TOKEN_EXPIRATION_LOGIN_LONG);
|
||||
$document->setAttribute('authSessionsLimit', $authValues['maxSessions'] ?? APP_LIMIT_USER_SESSIONS_DEFAULT);
|
||||
$document->setAttribute('authPasswordHistory', $authValues['passwordHistory'] ?? 0);
|
||||
$document->setAttribute('authPasswordDictionary', $authValues['passwordDictionary'] ?? false);
|
||||
|
||||
@@ -169,7 +169,6 @@ trait ProjectCustom
|
||||
$project = [
|
||||
'$id' => $project['body']['$id'],
|
||||
'name' => $project['body']['name'],
|
||||
'teamId' => $team['body']['$id'],
|
||||
'apiKey' => $key['body']['secret'],
|
||||
'devKey' => $devKey['body']['secret'],
|
||||
'webhookId' => $webhook['body']['$id'],
|
||||
|
||||
@@ -45,6 +45,7 @@ class AccountConsoleClientTest extends Scope
|
||||
$this->assertEquals($response['headers']['status-code'], 201);
|
||||
|
||||
$session = $response['cookies']['a_session_' . $this->getProject()['$id']];
|
||||
|
||||
// create team
|
||||
$team = $this->client->call(Client::METHOD_POST, '/teams', [
|
||||
'origin' => 'http://localhost',
|
||||
@@ -55,7 +56,6 @@ class AccountConsoleClientTest extends Scope
|
||||
'teamId' => 'unique()',
|
||||
'name' => 'myteam'
|
||||
]);
|
||||
|
||||
$this->assertEquals($team['headers']['status-code'], 201);
|
||||
|
||||
$teamId = $team['body']['$id'];
|
||||
|
||||
@@ -24,9 +24,10 @@ class ConsoleConsoleClientTest extends Scope
|
||||
], $this->getHeaders()));
|
||||
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
$this->assertCount(14, $response['body']);
|
||||
$this->assertCount(15, $response['body']);
|
||||
$this->assertIsString($response['body']['_APP_DOMAIN_TARGET_CNAME']);
|
||||
$this->assertIsString($response['body']['_APP_DOMAIN_TARGET_A']);
|
||||
$this->assertIsInt($response['body']['_APP_COMPUTE_BUILD_TIMEOUT']);
|
||||
$this->assertIsString($response['body']['_APP_DOMAIN_TARGET_AAAA']);
|
||||
$this->assertIsString($response['body']['_APP_DOMAIN_TARGET_CAA']);
|
||||
$this->assertIsInt($response['body']['_APP_STORAGE_LIMIT']);
|
||||
|
||||
@@ -1281,7 +1281,7 @@ trait DatabasesBase
|
||||
]);
|
||||
|
||||
$this->assertEquals(400, $fulltextReleaseYear['headers']['status-code']);
|
||||
$this->assertEquals($fulltextReleaseYear['body']['message'], 'Attribute "releaseYear" cannot be part of a FULLTEXT index, must be of type string');
|
||||
$this->assertEquals($fulltextReleaseYear['body']['message'], 'Attribute "releaseYear" cannot be part of a fulltext index, must be of type string');
|
||||
|
||||
$noAttributes = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $data['moviesId'] . '/indexes', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
@@ -1465,7 +1465,7 @@ trait DatabasesBase
|
||||
$this->assertEquals([128, 200], $index['body']['lengths']);
|
||||
|
||||
// Test case for lengths array overriding
|
||||
// set a length for an array attribute, it should get overriden with Database::ARRAY_INDEX_LENGTH
|
||||
// set a length for an array attribute, it should get overriden with Database::MAX_ARRAY_INDEX_LENGTH
|
||||
$create = $this->client->call(Client::METHOD_POST, "/databases/{$databaseId}/collections/{$collectionId}/indexes", [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
@@ -1483,7 +1483,7 @@ trait DatabasesBase
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||
]);
|
||||
$this->assertEquals([Database::ARRAY_INDEX_LENGTH], $index['body']['lengths']);
|
||||
$this->assertEquals([Database::MAX_ARRAY_INDEX_LENGTH], $index['body']['lengths']);
|
||||
|
||||
// Test case for count of lengths greater than attributes (should throw 400)
|
||||
$create = $this->client->call(Client::METHOD_POST, "/databases/{$databaseId}/collections/{$collectionId}/indexes", [
|
||||
|
||||
@@ -1281,7 +1281,7 @@ trait DatabasesBase
|
||||
]);
|
||||
|
||||
$this->assertEquals(400, $fulltextReleaseYear['headers']['status-code']);
|
||||
$this->assertEquals($fulltextReleaseYear['body']['message'], 'Attribute "releaseYear" cannot be part of a FULLTEXT index, must be of type string');
|
||||
$this->assertEquals($fulltextReleaseYear['body']['message'], 'Attribute "releaseYear" cannot be part of a fulltext index, must be of type string');
|
||||
|
||||
$noAttributes = $this->client->call(Client::METHOD_POST, '/tablesdb/' . $databaseId . '/tables/' . $data['moviesId'] . '/indexes', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace Tests\E2E\Services\Projects;
|
||||
|
||||
use Appwrite\Auth\Auth;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Tests\Async;
|
||||
use Tests\E2E\Client;
|
||||
@@ -865,7 +866,7 @@ class ProjectsConsoleClientTest extends Scope
|
||||
], $this->getHeaders()));
|
||||
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
$this->assertEquals(TOKEN_EXPIRATION_LOGIN_LONG, $response['body']['authDuration']); // 1 Year
|
||||
$this->assertEquals(Auth::TOKEN_EXPIRATION_LOGIN_LONG, $response['body']['authDuration']); // 1 Year
|
||||
|
||||
/**
|
||||
* Test for SUCCESS
|
||||
@@ -1008,7 +1009,7 @@ class ProjectsConsoleClientTest extends Scope
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'duration' => TOKEN_EXPIRATION_LOGIN_LONG,
|
||||
'duration' => Auth::TOKEN_EXPIRATION_LOGIN_LONG,
|
||||
]);
|
||||
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
@@ -1021,7 +1022,7 @@ class ProjectsConsoleClientTest extends Scope
|
||||
], $this->getHeaders()));
|
||||
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
$this->assertEquals(TOKEN_EXPIRATION_LOGIN_LONG, $response['body']['authDuration']); // 1 Year
|
||||
$this->assertEquals(Auth::TOKEN_EXPIRATION_LOGIN_LONG, $response['body']['authDuration']); // 1 Year
|
||||
|
||||
return ['projectId' => $projectId];
|
||||
}
|
||||
|
||||
+222
-46
@@ -4,7 +4,6 @@ namespace Tests\Unit\Auth;
|
||||
|
||||
use Appwrite\Auth\Auth;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Utopia\Auth\Proofs\Token;
|
||||
use Utopia\Database\DateTime;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Helpers\ID;
|
||||
@@ -23,25 +22,203 @@ class AuthTest extends TestCase
|
||||
Authorization::setRole(Role::any()->toString());
|
||||
}
|
||||
|
||||
public function testCookieName(): void
|
||||
{
|
||||
$name = 'cookie-name';
|
||||
|
||||
$this->assertEquals(Auth::setCookieName($name), $name);
|
||||
$this->assertEquals(Auth::$cookieName, $name);
|
||||
}
|
||||
|
||||
public function testEncodeDecodeSession(): void
|
||||
{
|
||||
$id = 'id';
|
||||
$secret = 'secret';
|
||||
$session = 'eyJpZCI6ImlkIiwic2VjcmV0Ijoic2VjcmV0In0=';
|
||||
|
||||
$this->assertEquals(Auth::encodeSession($id, $secret), $session);
|
||||
$this->assertEquals(Auth::decodeSession($session), ['id' => $id, 'secret' => $secret]);
|
||||
}
|
||||
|
||||
public function testHash(): void
|
||||
{
|
||||
$secret = 'secret';
|
||||
$this->assertEquals(Auth::hash($secret), '2bb80d537b1da3e38bd30361aa855686bde0eacd7162fef6a25fe97bf527a25b');
|
||||
}
|
||||
|
||||
public function testPassword(): void
|
||||
{
|
||||
/*
|
||||
General tests, using pre-defined hashes generated by online tools
|
||||
*/
|
||||
|
||||
// Bcrypt - Version Y
|
||||
$plain = 'secret';
|
||||
$hash = '$2y$08$PDbMtV18J1KOBI9tIYabBuyUwBrtXPGhLxCy9pWP6xkldVOKLrLKy';
|
||||
$generatedHash = Auth::passwordHash($plain, 'bcrypt');
|
||||
$this->assertEquals(true, Auth::passwordVerify($plain, $generatedHash, 'bcrypt'));
|
||||
$this->assertEquals(true, Auth::passwordVerify($plain, $hash, 'bcrypt'));
|
||||
$this->assertEquals(false, Auth::passwordVerify('wrongPassword', $hash, 'bcrypt'));
|
||||
|
||||
// Bcrypt - Version A
|
||||
$plain = 'test123';
|
||||
$hash = '$2a$12$3f2ZaARQ1AmhtQWx2nmQpuXcWfTj1YV2/Hl54e8uKxIzJe3IfwLiu';
|
||||
$generatedHash = Auth::passwordHash($plain, 'bcrypt');
|
||||
$this->assertEquals(true, Auth::passwordVerify($plain, $generatedHash, 'bcrypt'));
|
||||
$this->assertEquals(true, Auth::passwordVerify($plain, $hash, 'bcrypt'));
|
||||
$this->assertEquals(false, Auth::passwordVerify('wrongPassword', $hash, 'bcrypt'));
|
||||
|
||||
// Bcrypt - Cost 5
|
||||
$plain = 'hello-world';
|
||||
$hash = '$2a$05$IjrtSz6SN7UJ6Sh3l.b5jODEvEG2LMJTPAHIaLWRvlWx7if3VMkFO';
|
||||
$generatedHash = Auth::passwordHash($plain, 'bcrypt');
|
||||
$this->assertEquals(true, Auth::passwordVerify($plain, $generatedHash, 'bcrypt'));
|
||||
$this->assertEquals(true, Auth::passwordVerify($plain, $hash, 'bcrypt'));
|
||||
$this->assertEquals(false, Auth::passwordVerify('wrongPassword', $hash, 'bcrypt'));
|
||||
|
||||
// Bcrypt - Cost 15
|
||||
$plain = 'super-secret-password';
|
||||
$hash = '$2a$15$DS0ZzbsFZYumH/E4Qj5oeOHnBcM3nCCsCA2m4Goigat/0iMVQC4Na';
|
||||
$generatedHash = Auth::passwordHash($plain, 'bcrypt');
|
||||
$this->assertEquals(true, Auth::passwordVerify($plain, $generatedHash, 'bcrypt'));
|
||||
$this->assertEquals(true, Auth::passwordVerify($plain, $hash, 'bcrypt'));
|
||||
$this->assertEquals(false, Auth::passwordVerify('wrongPassword', $hash, 'bcrypt'));
|
||||
|
||||
// MD5 - Short
|
||||
$plain = 'appwrite';
|
||||
$hash = '144fa7eaa4904e8ee120651997f70dcc';
|
||||
$generatedHash = Auth::passwordHash($plain, 'md5');
|
||||
$this->assertEquals(true, Auth::passwordVerify($plain, $generatedHash, 'md5'));
|
||||
$this->assertEquals(true, Auth::passwordVerify($plain, $hash, 'md5'));
|
||||
$this->assertEquals(false, Auth::passwordVerify('wrongPassword', $hash, 'md5'));
|
||||
|
||||
// MD5 - Long
|
||||
$plain = 'AppwriteIsAwesomeBackendAsAServiceThatIsAlsoOpenSourced';
|
||||
$hash = '8410e96cf7ac64e0b84c3f8517a82616';
|
||||
$generatedHash = Auth::passwordHash($plain, 'md5');
|
||||
$this->assertEquals(true, Auth::passwordVerify($plain, $generatedHash, 'md5'));
|
||||
$this->assertEquals(true, Auth::passwordVerify($plain, $hash, 'md5'));
|
||||
$this->assertEquals(false, Auth::passwordVerify('wrongPassword', $hash, 'md5'));
|
||||
|
||||
// PHPass
|
||||
$plain = 'pass123';
|
||||
$hash = '$P$BVKPmJBZuLch27D4oiMRTEykGLQ9tX0';
|
||||
$generatedHash = Auth::passwordHash($plain, 'phpass');
|
||||
$this->assertEquals(true, Auth::passwordVerify($plain, $generatedHash, 'phpass'));
|
||||
$this->assertEquals(true, Auth::passwordVerify($plain, $hash, 'phpass'));
|
||||
$this->assertEquals(false, Auth::passwordVerify('wrongPassword', $hash, 'phpass'));
|
||||
|
||||
// SHA
|
||||
$plain = 'developersAreAwesome!';
|
||||
$hash = '2455118438cb125354b89bb5888346e9bd23355462c40df393fab514bf2220b5a08e4e2d7b85d7327595a450d0ac965cc6661152a46a157c66d681bed20a4735';
|
||||
$generatedHash = Auth::passwordHash($plain, 'sha');
|
||||
$this->assertEquals(true, Auth::passwordVerify($plain, $generatedHash, 'sha'));
|
||||
$this->assertEquals(true, Auth::passwordVerify($plain, $hash, 'sha'));
|
||||
$this->assertEquals(false, Auth::passwordVerify('wrongPassword', $hash, 'sha'));
|
||||
|
||||
// Argon2
|
||||
$plain = 'safe-argon-password';
|
||||
$hash = '$argon2id$v=19$m=2048,t=3,p=4$MWc5NWRmc2QxZzU2$41mp7rSgBZ49YxLbbxIac7aRaxfp5/e1G45ckwnK0g8';
|
||||
$generatedHash = Auth::passwordHash($plain, 'argon2');
|
||||
$this->assertEquals(true, Auth::passwordVerify($plain, $generatedHash, 'argon2'));
|
||||
$this->assertEquals(true, Auth::passwordVerify($plain, $hash, 'argon2'));
|
||||
$this->assertEquals(false, Auth::passwordVerify('wrongPassword', $hash, 'argon2'));
|
||||
|
||||
// Scrypt
|
||||
$plain = 'some-scrypt-password';
|
||||
$hash = 'b448ad7ba88b653b5b56b8053a06806724932d0751988bc9cd0ef7ff059e8ba8a020e1913b7069a650d3f99a1559aba0221f2c277826919513a054e76e339028';
|
||||
$generatedHash = Auth::passwordHash($plain, 'scrypt', [ 'salt' => 'some-salt', 'length' => 64, 'costCpu' => 16384, 'costMemory' => 12, 'costParallel' => 2]);
|
||||
|
||||
$this->assertEquals(true, Auth::passwordVerify($plain, $generatedHash, 'scrypt', [ 'salt' => 'some-salt', 'length' => 64, 'costCpu' => 16384, 'costMemory' => 12, 'costParallel' => 2]));
|
||||
$this->assertEquals(true, Auth::passwordVerify($plain, $hash, 'scrypt', [ 'salt' => 'some-salt', 'length' => 64, 'costCpu' => 16384, 'costMemory' => 12, 'costParallel' => 2]));
|
||||
$this->assertEquals(false, Auth::passwordVerify($plain, $hash, 'scrypt', [ 'salt' => 'some-wrong-salt', 'length' => 64, 'costCpu' => 16384, 'costMemory' => 12, 'costParallel' => 2]));
|
||||
$this->assertEquals(false, Auth::passwordVerify($plain, $hash, 'scrypt', [ 'salt' => 'some-salt', 'length' => 64, 'costCpu' => 16384, 'costMemory' => 10, 'costParallel' => 2]));
|
||||
$this->assertEquals(false, Auth::passwordVerify('wrongPassword', $hash, 'scrypt', [ 'salt' => 'some-salt', 'length' => 64, 'costCpu' => 16384, 'costMemory' => 12, 'costParallel' => 2]));
|
||||
|
||||
// ScryptModified tested are in provider-specific tests below
|
||||
|
||||
/*
|
||||
Provider-specific tests, ensuring functionality of specific use-cases
|
||||
*/
|
||||
|
||||
// Provider #1 (Database)
|
||||
$plain = 'example-password';
|
||||
$hash = '$2a$10$3bIGRWUes86CICsuchGLj.e.BqdCdg2/1Ud9LvBhJr0j7Dze8PBdS';
|
||||
$generatedHash = Auth::passwordHash($plain, 'bcrypt');
|
||||
$this->assertEquals(true, Auth::passwordVerify($plain, $generatedHash, 'bcrypt'));
|
||||
$this->assertEquals(true, Auth::passwordVerify($plain, $hash, 'bcrypt'));
|
||||
$this->assertEquals(false, Auth::passwordVerify('wrongPassword', $hash, 'bcrypt'));
|
||||
|
||||
// Provider #2 (Blog)
|
||||
$plain = 'your-password';
|
||||
$hash = '$P$BkiNDJTpAWXtpaMhEUhUdrv7M0I1g6.';
|
||||
$generatedHash = Auth::passwordHash($plain, 'phpass');
|
||||
$this->assertEquals(true, Auth::passwordVerify($plain, $generatedHash, 'phpass'));
|
||||
$this->assertEquals(true, Auth::passwordVerify($plain, $hash, 'phpass'));
|
||||
$this->assertEquals(false, Auth::passwordVerify('wrongPassword', $hash, 'phpass'));
|
||||
|
||||
// Provider #2 (Google)
|
||||
$plain = 'users-password';
|
||||
$hash = 'EPKgfALpS9Tvgr/y1ki7ubY4AEGJeWL3teakrnmOacN4XGiyD00lkzEHgqCQ71wGxoi/zb7Y9a4orOtvMV3/Jw==';
|
||||
$salt = '56dFqW+kswqktw==';
|
||||
$saltSeparator = 'Bw==';
|
||||
$signerKey = 'XyEKE9RcTDeLEsL/RjwPDBv/RqDl8fb3gpYEOQaPihbxf1ZAtSOHCjuAAa7Q3oHpCYhXSN9tizHgVOwn6krflQ==';
|
||||
|
||||
$options = [ 'salt' => $salt, 'saltSeparator' => $saltSeparator, 'signerKey' => $signerKey ];
|
||||
$generatedHash = Auth::passwordHash($plain, 'scryptMod', $options);
|
||||
$this->assertEquals(true, Auth::passwordVerify($plain, $generatedHash, 'scryptMod', $options));
|
||||
$this->assertEquals(true, Auth::passwordVerify($plain, $hash, 'scryptMod', $options));
|
||||
$this->assertEquals(false, Auth::passwordVerify('wrongPassword', $hash, 'scryptMod', $options));
|
||||
}
|
||||
|
||||
public function testUnknownAlgo()
|
||||
{
|
||||
$this->expectExceptionMessage('Hashing algorithm \'md8\' is not supported.');
|
||||
|
||||
// Bcrypt - Cost 5
|
||||
$plain = 'whatIsMd8?!?';
|
||||
$generatedHash = Auth::passwordHash($plain, 'md8');
|
||||
$this->assertEquals(true, Auth::passwordVerify($plain, $generatedHash, 'md8'));
|
||||
}
|
||||
|
||||
public function testPasswordGenerator(): void
|
||||
{
|
||||
$this->assertEquals(\mb_strlen(Auth::passwordGenerator()), 40);
|
||||
$this->assertEquals(\mb_strlen(Auth::passwordGenerator(5)), 10);
|
||||
}
|
||||
|
||||
public function testTokenGenerator(): void
|
||||
{
|
||||
$this->assertEquals(\strlen(Auth::tokenGenerator()), 256);
|
||||
$this->assertEquals(\strlen(Auth::tokenGenerator(5)), 5);
|
||||
}
|
||||
|
||||
public function testCodeGenerator(): void
|
||||
{
|
||||
$this->assertEquals(6, \strlen(Auth::codeGenerator()));
|
||||
$this->assertEquals(\mb_strlen(Auth::codeGenerator(256)), 256);
|
||||
$this->assertEquals(\mb_strlen(Auth::codeGenerator(10)), 10);
|
||||
$this->assertTrue(is_numeric(Auth::codeGenerator(5)));
|
||||
}
|
||||
|
||||
public function testSessionVerify(): void
|
||||
{
|
||||
$proofForToken = new Token();
|
||||
$expireTime1 = 60 * 60 * 24;
|
||||
|
||||
$secret = 'secret1';
|
||||
$hash = $proofForToken->hash($secret);
|
||||
$hash = Auth::hash($secret);
|
||||
$tokens1 = [
|
||||
new Document([
|
||||
'$id' => ID::custom('token1'),
|
||||
'secret' => $hash,
|
||||
'provider' => SESSION_PROVIDER_EMAIL,
|
||||
'provider' => Auth::SESSION_PROVIDER_EMAIL,
|
||||
'providerUid' => 'test@example.com',
|
||||
'expire' => DateTime::addSeconds(new \DateTime(), $expireTime1),
|
||||
]),
|
||||
new Document([
|
||||
'$id' => ID::custom('token2'),
|
||||
'secret' => 'secret2',
|
||||
'provider' => SESSION_PROVIDER_EMAIL,
|
||||
'provider' => Auth::SESSION_PROVIDER_EMAIL,
|
||||
'providerUid' => 'test@example.com',
|
||||
'expire' => DateTime::addSeconds(new \DateTime(), $expireTime1),
|
||||
]),
|
||||
@@ -53,40 +230,39 @@ class AuthTest extends TestCase
|
||||
new Document([ // Correct secret and type time, wrong expire time
|
||||
'$id' => ID::custom('token1'),
|
||||
'secret' => $hash,
|
||||
'provider' => SESSION_PROVIDER_EMAIL,
|
||||
'provider' => Auth::SESSION_PROVIDER_EMAIL,
|
||||
'providerUid' => 'test@example.com',
|
||||
'expire' => DateTime::addSeconds(new \DateTime(), $expireTime2),
|
||||
]),
|
||||
new Document([
|
||||
'$id' => ID::custom('token2'),
|
||||
'secret' => 'secret2',
|
||||
'provider' => SESSION_PROVIDER_EMAIL,
|
||||
'provider' => Auth::SESSION_PROVIDER_EMAIL,
|
||||
'providerUid' => 'test@example.com',
|
||||
'expire' => DateTime::addSeconds(new \DateTime(), $expireTime2),
|
||||
]),
|
||||
];
|
||||
|
||||
$this->assertEquals(Auth::sessionVerify($tokens1, $secret, $proofForToken), 'token1');
|
||||
$this->assertEquals(Auth::sessionVerify($tokens1, 'false-secret', $proofForToken), false);
|
||||
$this->assertEquals(Auth::sessionVerify($tokens2, $secret, $proofForToken), false);
|
||||
$this->assertEquals(Auth::sessionVerify($tokens2, 'false-secret', $proofForToken), false);
|
||||
$this->assertEquals(Auth::sessionVerify($tokens1, $secret), 'token1');
|
||||
$this->assertEquals(Auth::sessionVerify($tokens1, 'false-secret'), false);
|
||||
$this->assertEquals(Auth::sessionVerify($tokens2, $secret), false);
|
||||
$this->assertEquals(Auth::sessionVerify($tokens2, 'false-secret'), false);
|
||||
}
|
||||
|
||||
public function testTokenVerify(): void
|
||||
{
|
||||
$proofForToken = new Token();
|
||||
$secret = 'secret1';
|
||||
$hash = $proofForToken->hash($secret);
|
||||
$hash = Auth::hash($secret);
|
||||
$tokens1 = [
|
||||
new Document([
|
||||
'$id' => ID::custom('token1'),
|
||||
'type' => TOKEN_TYPE_RECOVERY,
|
||||
'type' => Auth::TOKEN_TYPE_RECOVERY,
|
||||
'expire' => DateTime::formatTz(DateTime::addSeconds(new \DateTime(), 60 * 60 * 24)),
|
||||
'secret' => $hash,
|
||||
]),
|
||||
new Document([
|
||||
'$id' => ID::custom('token2'),
|
||||
'type' => TOKEN_TYPE_RECOVERY,
|
||||
'type' => Auth::TOKEN_TYPE_RECOVERY,
|
||||
'expire' => DateTime::formatTz(DateTime::addSeconds(new \DateTime(), -60 * 60 * 24)),
|
||||
'secret' => 'secret2',
|
||||
]),
|
||||
@@ -95,13 +271,13 @@ class AuthTest extends TestCase
|
||||
$tokens2 = [
|
||||
new Document([ // Correct secret and type time, wrong expire time
|
||||
'$id' => ID::custom('token1'),
|
||||
'type' => TOKEN_TYPE_RECOVERY,
|
||||
'type' => Auth::TOKEN_TYPE_RECOVERY,
|
||||
'expire' => DateTime::formatTz(DateTime::addSeconds(new \DateTime(), -60 * 60 * 24)),
|
||||
'secret' => $hash,
|
||||
]),
|
||||
new Document([
|
||||
'$id' => ID::custom('token2'),
|
||||
'type' => TOKEN_TYPE_RECOVERY,
|
||||
'type' => Auth::TOKEN_TYPE_RECOVERY,
|
||||
'expire' => DateTime::formatTz(DateTime::addSeconds(new \DateTime(), -60 * 60 * 24)),
|
||||
'secret' => 'secret2',
|
||||
]),
|
||||
@@ -110,25 +286,25 @@ class AuthTest extends TestCase
|
||||
$tokens3 = [ // Correct secret and expire time, wrong type
|
||||
new Document([
|
||||
'$id' => ID::custom('token1'),
|
||||
'type' => TOKEN_TYPE_INVITE,
|
||||
'type' => Auth::TOKEN_TYPE_INVITE,
|
||||
'expire' => DateTime::formatTz(DateTime::addSeconds(new \DateTime(), 60 * 60 * 24)),
|
||||
'secret' => $hash,
|
||||
]),
|
||||
new Document([
|
||||
'$id' => ID::custom('token2'),
|
||||
'type' => TOKEN_TYPE_RECOVERY,
|
||||
'type' => Auth::TOKEN_TYPE_RECOVERY,
|
||||
'expire' => DateTime::formatTz(DateTime::addSeconds(new \DateTime(), -60 * 60 * 24)),
|
||||
'secret' => 'secret2',
|
||||
]),
|
||||
];
|
||||
|
||||
$this->assertEquals(Auth::tokenVerify($tokens1, TOKEN_TYPE_RECOVERY, $secret, $proofForToken), $tokens1[0]);
|
||||
$this->assertEquals(Auth::tokenVerify($tokens1, null, $secret, $proofForToken), $tokens1[0]);
|
||||
$this->assertEquals(Auth::tokenVerify($tokens1, TOKEN_TYPE_RECOVERY, 'false-secret', $proofForToken), false);
|
||||
$this->assertEquals(Auth::tokenVerify($tokens2, TOKEN_TYPE_RECOVERY, $secret, $proofForToken), false);
|
||||
$this->assertEquals(Auth::tokenVerify($tokens2, TOKEN_TYPE_RECOVERY, 'false-secret', $proofForToken), false);
|
||||
$this->assertEquals(Auth::tokenVerify($tokens3, TOKEN_TYPE_RECOVERY, $secret, $proofForToken), false);
|
||||
$this->assertEquals(Auth::tokenVerify($tokens3, TOKEN_TYPE_RECOVERY, 'false-secret', $proofForToken), false);
|
||||
$this->assertEquals(Auth::tokenVerify($tokens1, Auth::TOKEN_TYPE_RECOVERY, $secret), $tokens1[0]);
|
||||
$this->assertEquals(Auth::tokenVerify($tokens1, null, $secret), $tokens1[0]);
|
||||
$this->assertEquals(Auth::tokenVerify($tokens1, Auth::TOKEN_TYPE_RECOVERY, 'false-secret'), false);
|
||||
$this->assertEquals(Auth::tokenVerify($tokens2, Auth::TOKEN_TYPE_RECOVERY, $secret), false);
|
||||
$this->assertEquals(Auth::tokenVerify($tokens2, Auth::TOKEN_TYPE_RECOVERY, 'false-secret'), false);
|
||||
$this->assertEquals(Auth::tokenVerify($tokens3, Auth::TOKEN_TYPE_RECOVERY, $secret), false);
|
||||
$this->assertEquals(Auth::tokenVerify($tokens3, Auth::TOKEN_TYPE_RECOVERY, 'false-secret'), false);
|
||||
}
|
||||
|
||||
public function testIsPrivilegedUser(): void
|
||||
@@ -136,16 +312,16 @@ class AuthTest extends TestCase
|
||||
$this->assertEquals(false, Auth::isPrivilegedUser([]));
|
||||
$this->assertEquals(false, Auth::isPrivilegedUser([Role::guests()->toString()]));
|
||||
$this->assertEquals(false, Auth::isPrivilegedUser([Role::users()->toString()]));
|
||||
$this->assertEquals(true, Auth::isPrivilegedUser([USER_ROLE_ADMIN]));
|
||||
$this->assertEquals(true, Auth::isPrivilegedUser([USER_ROLE_DEVELOPER]));
|
||||
$this->assertEquals(true, Auth::isPrivilegedUser([USER_ROLE_OWNER]));
|
||||
$this->assertEquals(false, Auth::isPrivilegedUser([USER_ROLE_APPS]));
|
||||
$this->assertEquals(false, Auth::isPrivilegedUser([USER_ROLE_SYSTEM]));
|
||||
$this->assertEquals(true, Auth::isPrivilegedUser([Auth::USER_ROLE_ADMIN]));
|
||||
$this->assertEquals(true, Auth::isPrivilegedUser([Auth::USER_ROLE_DEVELOPER]));
|
||||
$this->assertEquals(true, Auth::isPrivilegedUser([Auth::USER_ROLE_OWNER]));
|
||||
$this->assertEquals(false, Auth::isPrivilegedUser([Auth::USER_ROLE_APPS]));
|
||||
$this->assertEquals(false, Auth::isPrivilegedUser([Auth::USER_ROLE_SYSTEM]));
|
||||
|
||||
$this->assertEquals(false, Auth::isPrivilegedUser([USER_ROLE_APPS, USER_ROLE_APPS]));
|
||||
$this->assertEquals(false, Auth::isPrivilegedUser([USER_ROLE_APPS, Role::guests()->toString()]));
|
||||
$this->assertEquals(true, Auth::isPrivilegedUser([USER_ROLE_OWNER, Role::guests()->toString()]));
|
||||
$this->assertEquals(true, Auth::isPrivilegedUser([USER_ROLE_OWNER, USER_ROLE_ADMIN, USER_ROLE_DEVELOPER]));
|
||||
$this->assertEquals(false, Auth::isPrivilegedUser([Auth::USER_ROLE_APPS, Auth::USER_ROLE_APPS]));
|
||||
$this->assertEquals(false, Auth::isPrivilegedUser([Auth::USER_ROLE_APPS, Role::guests()->toString()]));
|
||||
$this->assertEquals(true, Auth::isPrivilegedUser([Auth::USER_ROLE_OWNER, Role::guests()->toString()]));
|
||||
$this->assertEquals(true, Auth::isPrivilegedUser([Auth::USER_ROLE_OWNER, Auth::USER_ROLE_ADMIN, Auth::USER_ROLE_DEVELOPER]));
|
||||
}
|
||||
|
||||
public function testIsAppUser(): void
|
||||
@@ -153,16 +329,16 @@ class AuthTest extends TestCase
|
||||
$this->assertEquals(false, Auth::isAppUser([]));
|
||||
$this->assertEquals(false, Auth::isAppUser([Role::guests()->toString()]));
|
||||
$this->assertEquals(false, Auth::isAppUser([Role::users()->toString()]));
|
||||
$this->assertEquals(false, Auth::isAppUser([USER_ROLE_ADMIN]));
|
||||
$this->assertEquals(false, Auth::isAppUser([USER_ROLE_DEVELOPER]));
|
||||
$this->assertEquals(false, Auth::isAppUser([USER_ROLE_OWNER]));
|
||||
$this->assertEquals(true, Auth::isAppUser([USER_ROLE_APPS]));
|
||||
$this->assertEquals(false, Auth::isAppUser([USER_ROLE_SYSTEM]));
|
||||
$this->assertEquals(false, Auth::isAppUser([Auth::USER_ROLE_ADMIN]));
|
||||
$this->assertEquals(false, Auth::isAppUser([Auth::USER_ROLE_DEVELOPER]));
|
||||
$this->assertEquals(false, Auth::isAppUser([Auth::USER_ROLE_OWNER]));
|
||||
$this->assertEquals(true, Auth::isAppUser([Auth::USER_ROLE_APPS]));
|
||||
$this->assertEquals(false, Auth::isAppUser([Auth::USER_ROLE_SYSTEM]));
|
||||
|
||||
$this->assertEquals(true, Auth::isAppUser([USER_ROLE_APPS, USER_ROLE_APPS]));
|
||||
$this->assertEquals(true, Auth::isAppUser([USER_ROLE_APPS, Role::guests()->toString()]));
|
||||
$this->assertEquals(false, Auth::isAppUser([USER_ROLE_OWNER, Role::guests()->toString()]));
|
||||
$this->assertEquals(false, Auth::isAppUser([USER_ROLE_OWNER, USER_ROLE_ADMIN, USER_ROLE_DEVELOPER]));
|
||||
$this->assertEquals(true, Auth::isAppUser([Auth::USER_ROLE_APPS, Auth::USER_ROLE_APPS]));
|
||||
$this->assertEquals(true, Auth::isAppUser([Auth::USER_ROLE_APPS, Role::guests()->toString()]));
|
||||
$this->assertEquals(false, Auth::isAppUser([Auth::USER_ROLE_OWNER, Role::guests()->toString()]));
|
||||
$this->assertEquals(false, Auth::isAppUser([Auth::USER_ROLE_OWNER, Auth::USER_ROLE_ADMIN, Auth::USER_ROLE_DEVELOPER]));
|
||||
}
|
||||
|
||||
public function testGuestRoles(): void
|
||||
@@ -242,7 +418,7 @@ class AuthTest extends TestCase
|
||||
|
||||
public function testPrivilegedUserRoles(): void
|
||||
{
|
||||
Authorization::setRole(USER_ROLE_OWNER);
|
||||
Authorization::setRole(Auth::USER_ROLE_OWNER);
|
||||
$user = new Document([
|
||||
'$id' => ID::custom('123'),
|
||||
'emailVerification' => true,
|
||||
@@ -286,7 +462,7 @@ class AuthTest extends TestCase
|
||||
|
||||
public function testAppUserRoles(): void
|
||||
{
|
||||
Authorization::setRole(USER_ROLE_APPS);
|
||||
Authorization::setRole(Auth::USER_ROLE_APPS);
|
||||
$user = new Document([
|
||||
'$id' => ID::custom('123'),
|
||||
'memberships' => [
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
namespace Tests\Unit\Auth;
|
||||
|
||||
use Ahc\Jwt\JWT;
|
||||
use Appwrite\Auth\Auth;
|
||||
use Appwrite\Auth\Key;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Utopia\Config\Config;
|
||||
@@ -20,7 +21,7 @@ class KeyTest extends TestCase
|
||||
'collections.read',
|
||||
'documents.read',
|
||||
];
|
||||
$roleScopes = Config::getParam('roles', [])[USER_ROLE_APPS]['scopes'];
|
||||
$roleScopes = Config::getParam('roles', [])[Auth::USER_ROLE_APPS]['scopes'];
|
||||
|
||||
$key = static::generateKey($projectId, $usage, $scopes);
|
||||
$project = new Document(['$id' => $projectId,]);
|
||||
@@ -28,7 +29,7 @@ class KeyTest extends TestCase
|
||||
|
||||
$this->assertEquals($projectId, $decoded->getProjectId());
|
||||
$this->assertEquals(API_KEY_DYNAMIC, $decoded->getType());
|
||||
$this->assertEquals(USER_ROLE_APPS, $decoded->getRole());
|
||||
$this->assertEquals(Auth::USER_ROLE_APPS, $decoded->getRole());
|
||||
$this->assertEquals(\array_merge($scopes, $roleScopes), $decoded->getScopes());
|
||||
}
|
||||
|
||||
|
||||
@@ -59,7 +59,7 @@ class MessagingChannelsTest extends TestCase
|
||||
'confirm' => true,
|
||||
'roles' => [
|
||||
empty($index % 2)
|
||||
? USER_ROLE_ADMIN
|
||||
? Auth::USER_ROLE_ADMIN
|
||||
: 'member',
|
||||
]
|
||||
]
|
||||
@@ -294,7 +294,7 @@ class MessagingChannelsTest extends TestCase
|
||||
}
|
||||
|
||||
$role = empty($index % 2)
|
||||
? USER_ROLE_ADMIN
|
||||
? Auth::USER_ROLE_ADMIN
|
||||
: 'member';
|
||||
|
||||
$permissions = [
|
||||
|
||||
@@ -7,7 +7,7 @@ use PHPUnit\Framework\TestCase;
|
||||
use ReflectionMethod;
|
||||
use Utopia\Database\Document;
|
||||
|
||||
class MigrationTest extends TestCase
|
||||
abstract class MigrationTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* @var Migration
|
||||
|
||||
Reference in New Issue
Block a user