mirror of
https://github.com/appwrite/appwrite.git
synced 2026-05-26 13:51:13 +00:00
Merge branch 'master' of github.com:appwrite/appwrite into feature--flutter-support
This commit is contained in:
+7
-2
@@ -55,6 +55,10 @@ $utopia->init(function () use ($utopia, $request, $response, &$user, $project, $
|
||||
|
||||
$route = $utopia->match($request);
|
||||
|
||||
if(!empty($route->getLabel('sdk.platform', [])) && empty($project->getId())) {
|
||||
throw new Exception('Missing or unknown project ID', 400);
|
||||
}
|
||||
|
||||
$referrer = $request->getServer('HTTP_REFERER', '');
|
||||
$origin = parse_url($request->getServer('HTTP_ORIGIN', $referrer), PHP_URL_HOST);
|
||||
$protocol = parse_url($request->getServer('HTTP_ORIGIN', $referrer), PHP_URL_SCHEME);
|
||||
@@ -66,8 +70,9 @@ $utopia->init(function () use ($utopia, $request, $response, &$user, $project, $
|
||||
$selfDomain = new Domain(Config::getParam('domain'));
|
||||
$endDomain = new Domain($origin);
|
||||
|
||||
Config::setParam('domainVerification', ($selfDomain->getRegisterable() === $endDomain->getRegisterable()));
|
||||
|
||||
Config::setParam('domainVerification',
|
||||
($selfDomain->getRegisterable() === $endDomain->getRegisterable()));
|
||||
|
||||
/*
|
||||
* Security Headers
|
||||
*
|
||||
|
||||
@@ -29,6 +29,9 @@ use GeoIp2\Database\Reader;
|
||||
|
||||
include_once __DIR__ . '/../shared/api.php';
|
||||
|
||||
$oauthDefaultSuccess = Config::getParam('protocol').'://'.Config::getParam('domain').'/auth/oauth2/success';
|
||||
$oauthDefaultFailure = Config::getParam('protocol').'://'.Config::getParam('domain').'/auth/oauth2/failure';
|
||||
|
||||
$oauth2Keys = [];
|
||||
|
||||
$utopia->init(function() use (&$oauth2Keys) {
|
||||
@@ -247,8 +250,8 @@ $utopia->get('/v1/account/sessions/oauth2/:provider')
|
||||
->label('abuse-limit', 50)
|
||||
->label('abuse-key', 'ip:{ip}')
|
||||
->param('provider', '', function () { return new WhiteList(array_keys(Config::getParam('providers'))); }, 'OAuth2 Provider. Currently, supported providers are: ' . implode(', ', array_keys(array_filter(Config::getParam('providers'), function($node) {return (!$node['mock']);}))).'.')
|
||||
->param('success', '', function () use ($clients) { return new Host($clients); }, 'URL to redirect back to your app after a successful login attempt.')
|
||||
->param('failure', '', function () use ($clients) { return new Host($clients); }, 'URL to redirect back to your app after a failed login attempt.')
|
||||
->param('success', $oauthDefaultSuccess, function () use ($clients) { return new Host($clients); }, 'URL to redirect back to your app after a successful login attempt.', true)
|
||||
->param('failure', $oauthDefaultFailure, function () use ($clients) { return new Host($clients); }, 'URL to redirect back to your app after a failed login attempt.', true)
|
||||
->action(
|
||||
function ($provider, $success, $failure) use ($response, $request, $project) {
|
||||
$protocol = Config::getParam('protocol');
|
||||
@@ -275,7 +278,10 @@ $utopia->get('/v1/account/sessions/oauth2/:provider')
|
||||
|
||||
$oauth2 = new $classname($appId, $appSecret, $callback, ['success' => $success, 'failure' => $failure]);
|
||||
|
||||
$response->redirect($oauth2->getLoginURL());
|
||||
$response
|
||||
->addHeader('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0')
|
||||
->addHeader('Pragma', 'no-cache')
|
||||
->redirect($oauth2->getLoginURL());
|
||||
}
|
||||
);
|
||||
|
||||
@@ -292,8 +298,12 @@ $utopia->get('/v1/account/sessions/oauth2/callback/:provider/:projectId')
|
||||
function ($projectId, $provider, $code, $state) use ($response) {
|
||||
$domain = Config::getParam('domain');
|
||||
$protocol = Config::getParam('protocol');
|
||||
$response->redirect($protocol.'://'.$domain.'/v1/account/sessions/oauth2/'.$provider.'/redirect?'
|
||||
.http_build_query(['project' => $projectId, 'code' => $code, 'state' => $state]));
|
||||
|
||||
$response
|
||||
->addHeader('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0')
|
||||
->addHeader('Pragma', 'no-cache')
|
||||
->redirect($protocol.'://'.$domain.'/v1/account/sessions/oauth2/'.$provider.'/redirect?'
|
||||
.http_build_query(['project' => $projectId, 'code' => $code, 'state' => $state]));
|
||||
}
|
||||
);
|
||||
|
||||
@@ -309,7 +319,7 @@ $utopia->get('/v1/account/sessions/oauth2/:provider/redirect')
|
||||
->param('code', '', function () { return new Text(1024); }, 'OAuth2 code.')
|
||||
->param('state', '', function () { return new Text(2048); }, 'OAuth2 state params.', true)
|
||||
->action(
|
||||
function ($provider, $code, $state) use ($response, $request, $user, $projectDB, $project, $audit) {
|
||||
function ($provider, $code, $state) use ($response, $request, $user, $projectDB, $project, $audit, $oauthDefaultSuccess) {
|
||||
$protocol = Config::getParam('protocol');
|
||||
$callback = $protocol.'://'.$request->getServer('HTTP_HOST').'/v1/account/sessions/oauth2/callback/'.$provider.'/'.$project->getId();
|
||||
$defaultState = ['success' => $project->getAttribute('url', ''), 'failure' => ''];
|
||||
@@ -469,7 +479,13 @@ $utopia->get('/v1/account/sessions/oauth2/:provider/redirect')
|
||||
;
|
||||
}
|
||||
|
||||
if($state['success'] === $oauthDefaultSuccess) { // Add keys for non-web platforms
|
||||
$state['success'] = $state['success'].'/?end=true';
|
||||
}
|
||||
|
||||
$response
|
||||
->addHeader('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0')
|
||||
->addHeader('Pragma', 'no-cache')
|
||||
->addCookie(Auth::$cookieName.'_legacy', Auth::encodeSession($user->getId(), $secret), $expiry, '/', COOKIE_DOMAIN, ('https' == $protocol), true, null)
|
||||
->addCookie(Auth::$cookieName, Auth::encodeSession($user->getId(), $secret), $expiry, '/', COOKIE_DOMAIN, ('https' == $protocol), true, COOKIE_SAMESITE)
|
||||
->redirect($state['success'])
|
||||
|
||||
@@ -10,6 +10,7 @@ use Utopia\Validator\URL;
|
||||
use Utopia\Cache\Cache;
|
||||
use Utopia\Cache\Adapter\Filesystem;
|
||||
use Appwrite\Resize\Resize;
|
||||
use Appwrite\URL\URL as URLParse;
|
||||
use BaconQrCode\Renderer\ImageRenderer;
|
||||
use BaconQrCode\Renderer\Image\ImagickImageBackEnd;
|
||||
use BaconQrCode\Renderer\RendererStyle\RendererStyle;
|
||||
@@ -265,7 +266,7 @@ $utopia->get('/v1/avatars/favicon')
|
||||
$href = $link->getAttribute('href');
|
||||
$rel = $link->getAttribute('rel');
|
||||
$sizes = $link->getAttribute('sizes');
|
||||
$absolute = unparse_url(array_merge(parse_url($url), parse_url($href)));
|
||||
$absolute = URLParse::unparse(array_merge(parse_url($url), parse_url($href)));
|
||||
|
||||
switch (strtolower($rel)) {
|
||||
case 'icon':
|
||||
@@ -381,41 +382,4 @@ $utopia->get('/v1/avatars/qr')
|
||||
->send('', $writer->writeString($text))
|
||||
;
|
||||
}
|
||||
);
|
||||
|
||||
function unparse_url($parsed_url, $ommit = array())
|
||||
{
|
||||
if (isset($parsed_url['path']) && mb_substr($parsed_url['path'], 0, 1) !== '/') {
|
||||
$parsed_url['path'] = '/'.$parsed_url['path'];
|
||||
}
|
||||
|
||||
$p = array();
|
||||
|
||||
$p['scheme'] = isset($parsed_url['scheme']) ? $parsed_url['scheme'].'://' : '';
|
||||
|
||||
$p['host'] = isset($parsed_url['host']) ? $parsed_url['host'] : '';
|
||||
|
||||
$p['port'] = isset($parsed_url['port']) ? ':'.$parsed_url['port'] : '';
|
||||
|
||||
$p['user'] = isset($parsed_url['user']) ? $parsed_url['user'] : '';
|
||||
|
||||
$p['pass'] = isset($parsed_url['pass']) ? ':'.$parsed_url['pass'] : '';
|
||||
|
||||
$p['pass'] = ($p['user'] || $p['pass']) ? $p['pass'].'@' : '';
|
||||
|
||||
$p['path'] = isset($parsed_url['path']) ? $parsed_url['path'] : '';
|
||||
|
||||
$p['query'] = isset($parsed_url['query']) ? '?'.$parsed_url['query'] : '';
|
||||
|
||||
$p['fragment'] = isset($parsed_url['fragment']) ? '#'.$parsed_url['fragment'] : '';
|
||||
|
||||
if ($ommit) {
|
||||
foreach ($ommit as $key) {
|
||||
if (isset($p[ $key ])) {
|
||||
$p[ $key ] = '';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $p['scheme'].$p['user'].$p['pass'].$p['host'].$p['port'].$p['path'].$p['query'].$p['fragment'];
|
||||
}
|
||||
);
|
||||
@@ -108,6 +108,37 @@ $utopia->get('/auth/recovery/reset')
|
||||
->setParam('body', $page);
|
||||
});
|
||||
|
||||
|
||||
$utopia->get('/auth/oauth2/success')
|
||||
->desc('Registration page')
|
||||
->label('permission', 'public')
|
||||
->label('scope', 'home')
|
||||
->action(function () use ($layout) {
|
||||
$page = new View(__DIR__.'/../../views/home/auth/oauth2.phtml');
|
||||
|
||||
$layout
|
||||
->setParam('title', APP_NAME)
|
||||
->setParam('body', $page)
|
||||
->setParam('header', [])
|
||||
->setParam('footer', [])
|
||||
;
|
||||
});
|
||||
|
||||
$utopia->get('/auth/oauth2/failure')
|
||||
->desc('Registration page')
|
||||
->label('permission', 'public')
|
||||
->label('scope', 'home')
|
||||
->action(function () use ($layout) {
|
||||
$page = new View(__DIR__.'/../../views/home/auth/oauth2.phtml');
|
||||
|
||||
$layout
|
||||
->setParam('title', APP_NAME)
|
||||
->setParam('body', $page)
|
||||
->setParam('header', [])
|
||||
->setParam('footer', [])
|
||||
;
|
||||
});
|
||||
|
||||
$utopia->get('/error/:code')
|
||||
->desc('Error page')
|
||||
->label('permission', 'public')
|
||||
|
||||
+1
-1
@@ -32,7 +32,7 @@ const APP_EMAIL_SECURITY = 'security@localhost.test'; // Default security email
|
||||
const APP_USERAGENT = APP_NAME.'-Server v%s. Please report abuse at %s';
|
||||
const APP_MODE_ADMIN = 'admin';
|
||||
const APP_PAGING_LIMIT = 15;
|
||||
const APP_CACHE_BUSTER = 55;
|
||||
const APP_CACHE_BUSTER = 56;
|
||||
const APP_VERSION_STABLE = '0.5.3';
|
||||
const APP_STORAGE_UPLOADS = '/storage/uploads';
|
||||
const APP_STORAGE_CACHE = '/storage/cache';
|
||||
|
||||
@@ -6,7 +6,7 @@ $projectName = $this->getParam('projectName', '');
|
||||
$projectURL = $this->getParam('projectURL', '');
|
||||
?>
|
||||
|
||||
<section class="zone large margin-top-xl">
|
||||
<section class="zone large margin-top padding">
|
||||
<h1 class="margin-bottom">Error <?php echo $code; ?></h1>
|
||||
|
||||
<p><?php echo $message; ?></p>
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
<div class="zone large padding margin-top" id="message" style="display: none">
|
||||
<h1 class="margin-bottom">Missing Redirect URL</h1>
|
||||
<p>Your OAuth login flow is missing a redirect URL. Please check the <a href="https://<?php echo APP_DOMAIN; ?>/docs/account?platform=web&language=javascript#createOAuth2Session">OAuth docs</a> and send request for new session with a valid callback URL.</p>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
setTimeout(function () {
|
||||
document.getElementById('message').style.display = 'block';
|
||||
}, 25);
|
||||
|
||||
window.location = 'appwrite-callback://'+window.location.search;
|
||||
</script>
|
||||
<hr />
|
||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+1
-1
File diff suppressed because one or more lines are too long
@@ -1,4 +1,8 @@
|
||||
|
||||
.padding {
|
||||
padding: 30px;
|
||||
}
|
||||
|
||||
.margin-end {
|
||||
.func-margin-end(20px)!important;
|
||||
}
|
||||
|
||||
@@ -172,10 +172,14 @@ class MySQL extends Adapter
|
||||
|
||||
$st->execute();
|
||||
|
||||
$oldSignature = $st->fetch()['signature'];
|
||||
$result = $st->fetch();
|
||||
|
||||
if ($signature === $oldSignature) {
|
||||
return $data;
|
||||
if($result && isset($result['signature'])) {
|
||||
$oldSignature = $result['signature'];
|
||||
|
||||
if ($signature === $oldSignature) {
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\URL;
|
||||
|
||||
class URL
|
||||
{
|
||||
/**
|
||||
* Parse URL
|
||||
*
|
||||
* Take a URL string and split it to array parts
|
||||
*
|
||||
* @param $url string
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
static public function parse(string $url):array
|
||||
{
|
||||
$default = [
|
||||
'scheme' => '',
|
||||
'pass' => '',
|
||||
'user' => '',
|
||||
'host' => '',
|
||||
'port' => null,
|
||||
'path' => '',
|
||||
'query' => '',
|
||||
'fragment' => '',
|
||||
];
|
||||
|
||||
return array_merge($default, parse_url($url));
|
||||
}
|
||||
|
||||
/**
|
||||
* Un-Parse URL
|
||||
*
|
||||
* Take URL parts and combine them to a valid string
|
||||
*
|
||||
* @param $url array
|
||||
* @param $ommit array
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
static public function unparse(array $url, array $ommit = []):string
|
||||
{
|
||||
if (isset($url['path']) && mb_substr($url['path'], 0, 1) !== '/') {
|
||||
$url['path'] = '/'.$url['path'];
|
||||
}
|
||||
|
||||
$parts = [];
|
||||
|
||||
$parts['scheme'] = isset($url['scheme']) ? $url['scheme'].'://' : '';
|
||||
|
||||
$parts['host'] = isset($url['host']) ? $url['host'] : '';
|
||||
|
||||
$parts['port'] = isset($url['port']) ? ':'.$url['port'] : '';
|
||||
|
||||
$parts['user'] = isset($url['user']) ? $url['user'] : '';
|
||||
|
||||
$parts['pass'] = isset($url['pass']) ? ':'.$url['pass'] : '';
|
||||
|
||||
$parts['pass'] = ($parts['user'] || $parts['pass']) ? $parts['pass'].'@' : '';
|
||||
|
||||
$parts['path'] = isset($url['path']) ? $url['path'] : '';
|
||||
|
||||
$parts['query'] = isset($url['query']) && !empty($url['query']) ? '?'.$url['query'] : '';
|
||||
|
||||
$parts['fragment'] = isset($url['fragment']) ? '#'.$url['fragment'] : '';
|
||||
|
||||
if ($ommit) {
|
||||
foreach ($ommit as $key) {
|
||||
if (isset($parts[ $key ])) {
|
||||
$parts[ $key ] = '';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $parts['scheme'].$parts['user'].$parts['pass'].$parts['host'].$parts['port'].$parts['path'].$parts['query'].$parts['fragment'];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Tests;
|
||||
|
||||
use Appwrite\URL\URL;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class URLTest extends TestCase
|
||||
{
|
||||
public function testParse()
|
||||
{
|
||||
$url = URL::parse('https://appwrite.io:8080/path?query=string¶m=value');
|
||||
|
||||
$this->assertIsArray($url);
|
||||
$this->assertEquals('https', $url['scheme']);
|
||||
$this->assertEquals('appwrite.io', $url['host']);
|
||||
$this->assertEquals('8080', $url['port']);
|
||||
$this->assertEquals('/path', $url['path']);
|
||||
$this->assertEquals('query=string¶m=value', $url['query']);
|
||||
|
||||
$url = URL::parse('https://appwrite.io');
|
||||
|
||||
$this->assertIsArray($url);
|
||||
$this->assertEquals('https', $url['scheme']);
|
||||
$this->assertEquals('appwrite.io', $url['host']);
|
||||
$this->assertEquals(null, $url['port']);
|
||||
$this->assertEquals('', $url['path']);
|
||||
$this->assertEquals('', $url['query']);
|
||||
}
|
||||
|
||||
public function testUnparse()
|
||||
{
|
||||
$url = URL::unparse([
|
||||
'scheme' => 'https',
|
||||
'host' => 'appwrite.io',
|
||||
'port' => 8080,
|
||||
'path' => '/path',
|
||||
'query' => 'query=string¶m=value',
|
||||
]);
|
||||
|
||||
$this->assertIsString($url);
|
||||
$this->assertEquals('https://appwrite.io:8080/path?query=string¶m=value', $url);
|
||||
|
||||
$url = URL::unparse([
|
||||
'scheme' => 'https',
|
||||
'host' => 'appwrite.io',
|
||||
'port' => null,
|
||||
'path' => '/path',
|
||||
'query' => 'query=string¶m=value',
|
||||
]);
|
||||
|
||||
$this->assertIsString($url);
|
||||
$this->assertEquals('https://appwrite.io/path?query=string¶m=value', $url);
|
||||
|
||||
$url = URL::unparse([
|
||||
'scheme' => 'https',
|
||||
'host' => 'appwrite.io',
|
||||
'port' => null,
|
||||
'path' => '',
|
||||
'query' => '',
|
||||
]);
|
||||
|
||||
$this->assertIsString($url);
|
||||
$this->assertEquals('https://appwrite.io/', $url);
|
||||
|
||||
$url = URL::unparse([
|
||||
'scheme' => 'https',
|
||||
'host' => 'appwrite.io',
|
||||
'port' => null,
|
||||
'path' => '',
|
||||
'fragment' => 'bottom',
|
||||
]);
|
||||
|
||||
$this->assertIsString($url);
|
||||
$this->assertEquals('https://appwrite.io/#bottom', $url);
|
||||
|
||||
$url = URL::unparse([
|
||||
'scheme' => 'https',
|
||||
'user' => 'eldad',
|
||||
'pass' => 'fux',
|
||||
'host' => 'appwrite.io',
|
||||
'port' => null,
|
||||
'path' => '',
|
||||
'fragment' => 'bottom',
|
||||
]);
|
||||
|
||||
$this->assertIsString($url);
|
||||
$this->assertEquals('https://eldad:fux@appwrite.io/#bottom', $url);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user