diff --git a/app/app.php b/app/app.php index fb8a768474..8a15b78f41 100644 --- a/app/app.php +++ b/app/app.php @@ -3,7 +3,7 @@ // Init require_once __DIR__.'/init.php'; -global $env, $utopia, $request, $response, $register, $consoleDB, $project, $domain, $version, $service; +global $env, $utopia, $request, $response, $register, $consoleDB, $project, $domain, $version, $service, $protocol; use Utopia\App; use Utopia\Request; @@ -76,6 +76,7 @@ $utopia->init(function () use ($utopia, $request, $response, &$user, $project, $ ->addHeader('X-Content-Type-Options', 'nosniff') ->addHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, PATCH, DELETE') ->addHeader('Access-Control-Allow-Headers', 'Origin, Cookie, Set-Cookie, X-Requested-With, Content-Type, Access-Control-Allow-Origin, Access-Control-Request-Headers, Accept, X-Appwrite-Project, X-Appwrite-Key, X-Appwrite-Locale, X-Appwrite-Mode, X-SDK-Version') + ->addHeader('Access-Control-Expose-Headers', 'X-Fallback-Cookies') ->addHeader('Access-Control-Allow-Origin', $refDomain) ->addHeader('Access-Control-Allow-Credentials', 'true') ; @@ -234,7 +235,8 @@ $utopia->options(function () use ($request, $response, $domain, $project) { $response ->addHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, PATCH, DELETE') - ->addHeader('Access-Control-Allow-Headers', 'Origin, Cookie, Set-Cookie, X-Requested-With, Content-Type, Access-Control-Allow-Origin, Access-Control-Request-Headers, Accept, X-Appwrite-Project, X-Appwrite-Key, X-Appwrite-Locale, X-Appwrite-Mode, X-SDK-Version') + ->addHeader('Access-Control-Allow-Headers', 'Origin, Cookie, Set-Cookie, X-Requested-With, Content-Type, Access-Control-Allow-Origin, Access-Control-Request-Headers, Accept, X-Appwrite-Project, X-Appwrite-Key, X-Appwrite-Locale, X-Appwrite-Mode, X-SDK-Version, X-Fallback-Cookies') + ->addHeader('Access-Control-Expose-Headers', 'X-Fallback-Cookies') ->addHeader('Access-Control-Allow-Origin', $origin) ->addHeader('Access-Control-Allow-Credentials', 'true') ->send(); @@ -451,7 +453,7 @@ $utopia->get('/v1/open-api-2.json') ->param('extensions', 0, function () {return new Range(0, 1);}, 'Show extra data.', true) ->param('tests', 0, function () {return new Range(0, 1);}, 'Include only test services.', true) ->action( - function ($platform, $extensions, $tests) use ($response, $request, $utopia, $domain, $services) { + function ($platform, $extensions, $tests) use ($response, $request, $utopia, $domain, $services, $protocol) { function fromCamelCase($input) { preg_match_all('!([A-Z][A-Z0-9]*(?=$|[A-Z][a-z0-9])|[A-Za-z][a-z0-9]+)!', $input, $matches); @@ -586,7 +588,7 @@ $utopia->get('/v1/open-api-2.json') ], 'externalDocs' => [ 'description' => 'Full API docs, specs and tutorials', - 'url' => $request->getServer('REQUEST_SCHEME', 'https').'://'.$domain.'/docs', + 'url' => $protocol.'://'.$domain.'/docs', ], ]; diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index 19d3d9fff3..fea9cefcb9 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -1,7 +1,7 @@ post('/v1/account/sessions') ->param('email', '', function () { return new Email(); }, 'User email.') ->param('password', '', function () { return new Password(); }, 'User password.') ->action( - function ($email, $password) use ($response, $request, $projectDB, $audit, $webhook) { + function ($email, $password) use ($response, $request, $projectDB, $audit, $webhook, $protocol) { $profile = $projectDB->getCollection([ // Get user by email address 'limit' => 1, 'first' => true, @@ -212,8 +212,9 @@ $utopia->post('/v1/account/sessions') ; $response - ->addCookie(Auth::$cookieName.'_legacy', Auth::encodeSession($profile->getId(), $secret), $expiry, '/', COOKIE_DOMAIN, ('https' == $request->getServer('REQUEST_SCHEME', 'https')), true, null) - ->addCookie(Auth::$cookieName, Auth::encodeSession($profile->getId(), $secret), $expiry, '/', COOKIE_DOMAIN, ('https' == $request->getServer('REQUEST_SCHEME', 'https')), true, COOKIE_SAMESITE) + ->addCookie(Auth::$cookieName.'_legacy', Auth::encodeSession($profile->getId(), $secret), $expiry, '/', COOKIE_DOMAIN, ('https' == $protocol), true, null) + ->addCookie(Auth::$cookieName, Auth::encodeSession($profile->getId(), $secret), $expiry, '/', COOKIE_DOMAIN, ('https' == $protocol), true, COOKIE_SAMESITE) + ->addHeader('X-Fallback-Cookies', json_encode([Auth::$cookieName => Auth::encodeSession($profile->getId(), $secret)])) ->setStatusCode(Response::STATUS_CODE_CREATED) ->json($session->getArrayCopy(['$id', 'type', 'expire'])) ; @@ -237,8 +238,8 @@ $utopia->get('/v1/account/sessions/oauth2/:provider') ->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.') ->action( - function ($provider, $success, $failure) use ($response, $request, $project) { - $callback = $request->getServer('REQUEST_SCHEME', 'https').'://'.$request->getServer('HTTP_HOST').'/v1/account/sessions/oauth2/callback/'.$provider.'/'.$project->getId(); + function ($provider, $success, $failure) use ($response, $request, $project, $protocol) { + $callback = $protocol.'://'.$request->getServer('HTTP_HOST').'/v1/account/sessions/oauth2/callback/'.$provider.'/'.$project->getId(); $appId = $project->getAttribute('usersOauth2'.ucfirst($provider).'Appid', ''); $appSecret = $project->getAttribute('usersOauth2'.ucfirst($provider).'Secret', '{}'); @@ -275,8 +276,8 @@ $utopia->get('/v1/account/sessions/oauth2/callback/:provider/:projectId') ->param('code', '', function () { return new Text(1024); }, 'OAuth2 code.') ->param('state', '', function () { return new Text(2048); }, 'Login state params.', true) ->action( - function ($projectId, $provider, $code, $state) use ($response, $request, $domain) { - $response->redirect($request->getServer('REQUEST_SCHEME', 'https').'://'.$domain.'/v1/account/sessions/oauth2/'.$provider.'/redirect?' + function ($projectId, $provider, $code, $state) use ($response, $request, $domain, $protocol) { + $response->redirect($protocol.'://'.$domain.'/v1/account/sessions/oauth2/'.$provider.'/redirect?' .http_build_query(['project' => $projectId, 'code' => $code, 'state' => $state])); } ); @@ -293,8 +294,8 @@ $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) { - $callback = $request->getServer('REQUEST_SCHEME', 'https').'://'.$request->getServer('HTTP_HOST').'/v1/account/sessions/oauth2/callback/'.$provider.'/'.$project->getId(); + function ($provider, $code, $state) use ($response, $request, $user, $projectDB, $project, $audit, $protocol) { + $callback = $protocol.'://'.$request->getServer('HTTP_HOST').'/v1/account/sessions/oauth2/callback/'.$provider.'/'.$project->getId(); $defaultState = ['success' => $project->getAttribute('url', ''), 'failure' => '']; $validateURL = new URL(); @@ -443,8 +444,9 @@ $utopia->get('/v1/account/sessions/oauth2/:provider/redirect') ; $response - ->addCookie(Auth::$cookieName.'_legacy', Auth::encodeSession($user->getId(), $secret), $expiry, '/', COOKIE_DOMAIN, ('https' == $request->getServer('REQUEST_SCHEME', 'https')), true, null) - ->addCookie(Auth::$cookieName, Auth::encodeSession($user->getId(), $secret), $expiry, '/', COOKIE_DOMAIN, ('https' == $request->getServer('REQUEST_SCHEME', 'https')), true, COOKIE_SAMESITE) + ->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) + ->addHeader('X-Fallback-Cookies', json_encode([Auth::$cookieName => Auth::encodeSession($user->getId(), $secret)])) ->redirect($state['success']) ; } @@ -810,7 +812,7 @@ $utopia->delete('/v1/account') ->label('sdk.method', 'delete') ->label('sdk.description', '/docs/references/account/delete.md') ->action( - function () use ($response, $request, $user, $projectDB, $audit, $webhook) { + function () use ($response, $user, $projectDB, $audit, $webhook, $protocol) { $user = $projectDB->updateDocument(array_merge($user->getArrayCopy(), [ 'status' => Auth::USER_STATUS_BLOCKED, ])); @@ -842,8 +844,9 @@ $utopia->delete('/v1/account') ; $response - ->addCookie(Auth::$cookieName.'_legacy', '', time() - 3600, '/', COOKIE_DOMAIN, ('https' == $request->getServer('REQUEST_SCHEME', 'https')), true, null) - ->addCookie(Auth::$cookieName, '', time() - 3600, '/', COOKIE_DOMAIN, ('https' == $request->getServer('REQUEST_SCHEME', 'https')), true, COOKIE_SAMESITE) + ->addCookie(Auth::$cookieName.'_legacy', '', time() - 3600, '/', COOKIE_DOMAIN, ('https' == $protocol), true, null) + ->addCookie(Auth::$cookieName, '', time() - 3600, '/', COOKIE_DOMAIN, ('https' == $protocol), true, COOKIE_SAMESITE) + ->addHeader('X-Fallback-Cookies', json_encode([])) ->noContent() ; } @@ -860,7 +863,7 @@ $utopia->delete('/v1/account/sessions/:sessionId') ->label('abuse-limit', 100) ->param('sessionId', null, function () { return new UID(); }, 'Session unique ID. Use the string \'current\' to delete the current device session.') ->action( - function ($sessionId) use ($response, $request, $user, $projectDB, $webhook, $audit) { + function ($sessionId) use ($response, $user, $projectDB, $webhook, $audit, $protocol) { $sessionId = ($sessionId === 'current') ? Auth::tokenVerify($user->getAttribute('tokens'), Auth::TOKEN_TYPE_LOGIN, Auth::$secret) : $sessionId; @@ -888,8 +891,9 @@ $utopia->delete('/v1/account/sessions/:sessionId') if ($token->getAttribute('secret') == Auth::hash(Auth::$secret)) { // If current session delete the cookies too $response - ->addCookie(Auth::$cookieName.'_legacy', '', time() - 3600, '/', COOKIE_DOMAIN, ('https' == $request->getServer('REQUEST_SCHEME', 'https')), true, null) - ->addCookie(Auth::$cookieName, '', time() - 3600, '/', COOKIE_DOMAIN, ('https' == $request->getServer('REQUEST_SCHEME', 'https')), true, COOKIE_SAMESITE) + ->addCookie(Auth::$cookieName.'_legacy', '', time() - 3600, '/', COOKIE_DOMAIN, ('https' == $protocol), true, null) + ->addCookie(Auth::$cookieName, '', time() - 3600, '/', COOKIE_DOMAIN, ('https' == $protocol), true, COOKIE_SAMESITE) + ->addHeader('X-Fallback-Cookies', json_encode([])) ; } @@ -911,7 +915,7 @@ $utopia->delete('/v1/account/sessions') ->label('sdk.description', '/docs/references/account/delete-sessions.md') ->label('abuse-limit', 100) ->action( - function () use ($response, $request, $user, $projectDB, $audit, $webhook) { + function () use ($response, $user, $projectDB, $audit, $webhook, $protocol) { $tokens = $user->getAttribute('tokens', []); foreach ($tokens as $token) { /* @var $token Document */ @@ -934,8 +938,9 @@ $utopia->delete('/v1/account/sessions') if ($token->getAttribute('secret') == Auth::hash(Auth::$secret)) { // If current session delete the cookies too $response - ->addCookie(Auth::$cookieName.'_legacy', '', time() - 3600, '/', COOKIE_DOMAIN, ('https' == $request->getServer('REQUEST_SCHEME', 'https')), true, null) - ->addCookie(Auth::$cookieName, '', time() - 3600, '/', COOKIE_DOMAIN, ('https' == $request->getServer('REQUEST_SCHEME', 'https')), true, COOKIE_SAMESITE) + ->addCookie(Auth::$cookieName.'_legacy', '', time() - 3600, '/', COOKIE_DOMAIN, ('https' == $protocol), true, null) + ->addCookie(Auth::$cookieName, '', time() - 3600, '/', COOKIE_DOMAIN, ('https' == $protocol), true, COOKIE_SAMESITE) + ->addHeader('X-Fallback-Cookies', json_encode([])) ; } } diff --git a/app/controllers/api/teams.php b/app/controllers/api/teams.php index d14e9fb046..0897f74148 100644 --- a/app/controllers/api/teams.php +++ b/app/controllers/api/teams.php @@ -426,7 +426,7 @@ $utopia->patch('/v1/teams/:teamId/memberships/:inviteId/status') ->param('userId', '', function () { return new UID(); }, 'User unique ID.') ->param('secret', '', function () { return new Text(256); }, 'Secret key.') ->action( - function ($teamId, $inviteId, $userId, $secret) use ($response, $request, $user, $audit, $projectDB) { + function ($teamId, $inviteId, $userId, $secret) use ($response, $request, $user, $audit, $projectDB, $protocol) { $membership = $projectDB->getDocument($inviteId); if (empty($membership->getId()) || Database::SYSTEM_COLLECTION_MEMBERSHIPS != $membership->getCollection()) { @@ -521,8 +521,9 @@ $utopia->patch('/v1/teams/:teamId/memberships/:inviteId/status') ; $response - ->addCookie(Auth::$cookieName.'_legacy', Auth::encodeSession($user->getId(), $secret), $expiry, '/', COOKIE_DOMAIN, ('https' == $request->getServer('REQUEST_SCHEME', 'https')), true, null) - ->addCookie(Auth::$cookieName, Auth::encodeSession($user->getId(), $secret), $expiry, '/', COOKIE_DOMAIN, ('https' == $request->getServer('REQUEST_SCHEME', 'https')), true, COOKIE_SAMESITE) + ->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) + ->addHeader('X-Fallback-Cookies', json_encode([Auth::$cookieName => Auth::encodeSession($user->getId(), $secret)])) ->json(array_merge($membership->getArrayCopy([ '$id', 'userId', diff --git a/app/controllers/shared/web.php b/app/controllers/shared/web.php index e23dc4f9c3..e2ef34104f 100644 --- a/app/controllers/shared/web.php +++ b/app/controllers/shared/web.php @@ -3,6 +3,8 @@ use Utopia\View; use Utopia\Locale\Locale; +global $protocol; + Locale::$exceptions = false; $roles = [ @@ -20,7 +22,7 @@ if (!empty($request->getQuery('version', ''))) { $layout ->setParam('title', APP_NAME) - ->setParam('protocol', $request->getServer('REQUEST_SCHEME', 'https')) + ->setParam('protocol', $protocol) ->setParam('domain', $domain) ->setParam('home', $request->getServer('_APP_HOME')) ->setParam('setup', $request->getServer('_APP_SETUP')) diff --git a/app/init.php b/app/init.php index d74c3bfd40..c2d547f3e7 100644 --- a/app/init.php +++ b/app/init.php @@ -55,8 +55,8 @@ $collections = include __DIR__.'/../app/config/collections.php'; // Collections $redisHost = $request->getServer('_APP_REDIS_HOST', ''); $redisPort = $request->getServer('_APP_REDIS_PORT', ''); $utopia = new App('Asia/Tel_Aviv', $env); -$scheme = $request->getServer('REQUEST_SCHEME', ''); -$port = (string) parse_url($scheme.'://'.$request->getServer('HTTP_HOST', ''), PHP_URL_PORT); +$protocol = $request->getServer('HTTP_X_FORWARDED_PROTO', $request->getServer('REQUEST_SCHEME', 'https')); +$port = (string) parse_url($protocol.'://'.$request->getServer('HTTP_HOST', ''), PHP_URL_PORT); Resque::setBackend($redisHost.':'.$redisPort); @@ -67,7 +67,7 @@ define('COOKIE_DOMAIN', (filter_var($request->getServer('HTTP_HOST', null), FILTER_VALIDATE_IP) !== false) ) ? null - : '.'.parse_url($scheme.'://'.$request->getServer('HTTP_HOST', ''), PHP_URL_HOST)); + : '.'.parse_url($protocol.'://'.$request->getServer('HTTP_HOST', ''), PHP_URL_HOST)); define('COOKIE_SAMESITE', Response::COOKIE_SAMESITE_NONE); /* @@ -240,7 +240,18 @@ if (APP_MODE_ADMIN === $mode) { $session = Auth::decodeSession( $request->getCookie(Auth::$cookieName, // Get sessions $request->getCookie(Auth::$cookieName.'_legacy', // Get fallback session from old clients (no SameSite support) - $request->getHeader('X-Appwrite-Key', '')))); // Get API Key + $request->getHeader('X-Appwrite-Key', '')))); // Get API Key + +// Get fallback session from clients who block 3rd-party cookies +$response->addHeader('X-Debug-Fallback', 'false'); + +if(empty($session['id']) && empty($session['secret'])) { + $response->addHeader('X-Debug-Fallback', 'true'); + $fallback = $request->getHeader('X-Fallback-Cookies', null); + $fallback = json_decode($fallback, true); + $session = Auth::decodeSession(((isset($fallback[Auth::$cookieName])) ? $fallback[Auth::$cookieName] : '')); +} + Auth::$unique = $session['id']; Auth::$secret = $session['secret']; diff --git a/public/dist/scripts/app-all.js b/public/dist/scripts/app-all.js index fa3708aeb3..7d0c89196f 100644 --- a/public/dist/scripts/app-all.js +++ b/public/dist/scripts/app-all.js @@ -9,12 +9,14 @@ return str.join("&");};let addGlobalHeader=function(key,value){globalHeaders[key if(typeof path!=='string'){throw new Error('var path must be of type string');} if(typeof headers!=='object'){throw new Error('var headers must be of type object');} for(i=0;i=request.status){let data=request.response;let contentType=this.getResponseHeader('content-type')||'';contentType=contentType.substring(0,contentType.indexOf(';'));switch(contentType){case'application/json':data=JSON.parse(data);break;} +let cookieFallback=this.getResponseHeader('X-Fallback-Cookies')||'';if(window.localStorage&&cookieFallback){window.console.warn('Appwrite is using localStorage for session management. Increase your security by adding a custom domain as your API endpoint.');window.localStorage.setItem('cookieFallback',cookieFallback);} resolve(data);}else{reject(new Error(request.statusText));}};if(progress){request.addEventListener('progress',progress);request.upload.addEventListener('progress',progress,false);} request.onerror=function(){reject(new Error("Network Error"));};request.send(params);})};return{'get':function(path,headers={},params={}){return call('GET',path+((Object.keys(params).length>0)?'?'+buildQuery(params):''),headers,{});},'post':function(path,headers={},params={},progress=null){return call('POST',path,headers,params,progress);},'put':function(path,headers={},params={},progress=null){return call('PUT',path,headers,params,progress);},'patch':function(path,headers={},params={},progress=null){return call('PATCH',path,headers,params,progress);},'delete':function(path,headers={},params={},progress=null){return call('DELETE',path,headers,params,progress);},'addGlobalParam':addGlobalParam,'addGlobalHeader':addGlobalHeader}}(window.document);let iframe=function(method,url,params){let form=document.createElement('form');form.setAttribute('method',method);form.setAttribute('action',config.endpoint+url);for(let key in params){if(params.hasOwnProperty(key)){let hiddenField=document.createElement("input");hiddenField.setAttribute("type","hidden");hiddenField.setAttribute("name",key);hiddenField.setAttribute("value",params[key]);form.appendChild(hiddenField);}} document.body.appendChild(form);return form.submit();};let account={get:function(){let path='/account';let payload={};return http.get(path,{'content-type':'application/json',},payload);},create:function(email,password,name=''){if(email===undefined){throw new Error('Missing required parameter: "email"');} @@ -2405,7 +2407,7 @@ children[prop]=template.cloneNode(true);element.appendChild(children[prop]);(ind container.set('$index',originalIndex,true,false);container.set('$prefix',originalPrefix,true,false);container.set('$as',originalAs,true,false);element.dispatchEvent(new Event('looped'));};let template=(element.children.length===1)?element.children[0]:window.document.createElement('li');echo();container.bind(element,expr+'.length',echo);let path=(expr+'.length').split('.');while(path.length){container.bind(element,path.join('.'),echo);path.pop();}}});window.ls.container.get('view').add({selector:'data-ls-template',template:false,repeat:true,controller:function(element,view,http,expression,document){let template=expression.parse(element.getAttribute('data-ls-template'));let type=element.getAttribute('data-type')||'url';element.innerHTML='';if('script'===type){let inlineTemplate=document.getElementById(template);if(inlineTemplate&&inlineTemplate.innerHTML){element.innerHTML=inlineTemplate.innerHTML;element.dispatchEvent(new CustomEvent('template-loaded',{bubbles:true,cancelable:false}));} else{element.innerHTML='Missing template "'+template+'"';} return;} -http.get(template).then(function(element){return function(data){element.innerHTML=data;view.render(element);element.dispatchEvent(new CustomEvent('template-loaded',{bubbles:true,cancelable:false}));}}(element),function(){throw new Error('Failed loading template');});}});window.ls.error=function(){return function(error){console.error("ERROR-APP",error);};};window.addEventListener("error",function(event){console.error("ERROR-EVENT:",event.error.message,event.error.stack);});document.addEventListener("account.deleteSession",function(){window.location="/auth/signin";});document.addEventListener("account.create",function(){let container=window.ls.container;let form=container.get('serviceForm');let sdk=container.get('console');let promise=sdk.account.createSession(form.email,form.password);container.set("serviceForm",{},true,true);promise.then(function(){window.location='/console';},function(error){window.location='/auth/signup?failure=1';});});(function(window){"use strict";window.ls.container.set('alerts',function(window){return{list:[],ids:0,counter:0,max:5,add:function(message,time){var scope=this;message.id=scope.ids++;scope.counter++;scope.list.unshift(message);if(scope.counter>scope.max){scope.list.pop();scope.counter--;} +http.get(template).then(function(element){return function(data){element.innerHTML=data;view.render(element);element.dispatchEvent(new CustomEvent('template-loaded',{bubbles:true,cancelable:false}));}}(element),function(){throw new Error('Failed loading template');});}});window.ls.error=function(){return function(error){console.error("ERROR-APP",error);};};window.addEventListener("error",function(event){console.error("ERROR-EVENT:",event.error.message,event.error.stack);});document.addEventListener("account.deleteSession",function(){return;window.location="/auth/signin";});document.addEventListener("account.create",function(){let container=window.ls.container;let form=container.get('serviceForm');let sdk=container.get('console');let promise=sdk.account.createSession(form.email,form.password);container.set("serviceForm",{},true,true);promise.then(function(){window.location='/console';},function(error){window.location='/auth/signup?failure=1';});});(function(window){"use strict";window.ls.container.set('alerts',function(window){return{list:[],ids:0,counter:0,max:5,add:function(message,time){var scope=this;message.id=scope.ids++;scope.counter++;scope.list.unshift(message);if(scope.counter>scope.max){scope.list.pop();scope.counter--;} if(time>0){window.setTimeout(function(message){return function(){scope.remove(message.id)}}(message),time);} return message.id;},remove:function(id){let scope=this;for(let index=0;indexelement.trim()):[];failure=failure&&failure!=""?failure.split(",").map(element=>element.trim()):[];if(debug) +element.innerHTML="";element.appendChild(child);container.set("chart",new Chart(child.getContext("2d"),config),true);element.dataset["canvas"]=true;}});(function(window){"use strict";window.ls.view.add({selector:"data-service",controller:function(element,view,container,form,alerts,expression,window){let action=element.dataset["service"];let service=element.dataset["name"]||action;let event=expression.parse(element.dataset["event"]);let confirm=element.dataset["confirm"]||"";let loading=element.dataset["loading"]||"";let loaderId=null;let scope=element.dataset["scope"]||"sdk";let debug=!!element.dataset["debug"];let success=element.dataset["success"]||"";let failure=element.dataset["failure"]||"";success=success&&success!=""?success.split(",").map(element=>element.trim()):[];failure=failure&&failure!=""?failure.split(",").map(element=>element.trim()):[];if(debug) console.log("%c[service init]: "+action+" ("+service+")","color:red");let callbacks={reset:function(){return function(){if("FORM"===element.tagName){return element.reset();} throw new Error("This callback is only valid for forms");};},alert:function(text,classname){return function(alerts){alerts.add({text:text,class:classname||"success"},3000);};},redirect:function(url){return function(router){window.location=url||"/";};},reload:function(){return function(router){router.reload();};},state:function(keys){let updateQueryString=function(key,value,url){var re=new RegExp("([?&])"+key+"=.*?(&|#|$)(.*)","gi"),hash;if(re.test(url)){if(typeof value!=="undefined"&&value!==null){return url.replace(re,"$1"+key+"="+value+"$2$3");}else{hash=url.split("#");url=hash[0].replace(re,"$1$3").replace(/(&|\?)$/,"");if(typeof hash[1]!=="undefined"&&hash[1]!==null){url+="#"+hash[1];} return url;}}else{if(typeof value!=="undefined"&&value!==null){var separator=url.indexOf("?")!==-1?"&":"?";hash=url.split("#");url=hash[0]+separator+key+"="+value;if(typeof hash[1]!=="undefined"&&hash[1]!==null){url+="#"+hash[1];} diff --git a/public/dist/scripts/app-dep.js b/public/dist/scripts/app-dep.js index e0dbacb952..11466a19a7 100644 --- a/public/dist/scripts/app-dep.js +++ b/public/dist/scripts/app-dep.js @@ -9,12 +9,14 @@ return str.join("&");};let addGlobalHeader=function(key,value){globalHeaders[key if(typeof path!=='string'){throw new Error('var path must be of type string');} if(typeof headers!=='object'){throw new Error('var headers must be of type object');} for(i=0;i=request.status){let data=request.response;let contentType=this.getResponseHeader('content-type')||'';contentType=contentType.substring(0,contentType.indexOf(';'));switch(contentType){case'application/json':data=JSON.parse(data);break;} +let cookieFallback=this.getResponseHeader('X-Fallback-Cookies')||'';if(window.localStorage&&cookieFallback){window.console.warn('Appwrite is using localStorage for session management. Increase your security by adding a custom domain as your API endpoint.');window.localStorage.setItem('cookieFallback',cookieFallback);} resolve(data);}else{reject(new Error(request.statusText));}};if(progress){request.addEventListener('progress',progress);request.upload.addEventListener('progress',progress,false);} request.onerror=function(){reject(new Error("Network Error"));};request.send(params);})};return{'get':function(path,headers={},params={}){return call('GET',path+((Object.keys(params).length>0)?'?'+buildQuery(params):''),headers,{});},'post':function(path,headers={},params={},progress=null){return call('POST',path,headers,params,progress);},'put':function(path,headers={},params={},progress=null){return call('PUT',path,headers,params,progress);},'patch':function(path,headers={},params={},progress=null){return call('PATCH',path,headers,params,progress);},'delete':function(path,headers={},params={},progress=null){return call('DELETE',path,headers,params,progress);},'addGlobalParam':addGlobalParam,'addGlobalHeader':addGlobalHeader}}(window.document);let iframe=function(method,url,params){let form=document.createElement('form');form.setAttribute('method',method);form.setAttribute('action',config.endpoint+url);for(let key in params){if(params.hasOwnProperty(key)){let hiddenField=document.createElement("input");hiddenField.setAttribute("type","hidden");hiddenField.setAttribute("name",key);hiddenField.setAttribute("value",params[key]);form.appendChild(hiddenField);}} document.body.appendChild(form);return form.submit();};let account={get:function(){let path='/account';let payload={};return http.get(path,{'content-type':'application/json',},payload);},create:function(email,password,name=''){if(email===undefined){throw new Error('Missing required parameter: "email"');} diff --git a/public/dist/scripts/app.js b/public/dist/scripts/app.js index 66a81bc225..cd8708932e 100644 --- a/public/dist/scripts/app.js +++ b/public/dist/scripts/app.js @@ -123,7 +123,7 @@ children[prop]=template.cloneNode(true);element.appendChild(children[prop]);(ind container.set('$index',originalIndex,true,false);container.set('$prefix',originalPrefix,true,false);container.set('$as',originalAs,true,false);element.dispatchEvent(new Event('looped'));};let template=(element.children.length===1)?element.children[0]:window.document.createElement('li');echo();container.bind(element,expr+'.length',echo);let path=(expr+'.length').split('.');while(path.length){container.bind(element,path.join('.'),echo);path.pop();}}});window.ls.container.get('view').add({selector:'data-ls-template',template:false,repeat:true,controller:function(element,view,http,expression,document){let template=expression.parse(element.getAttribute('data-ls-template'));let type=element.getAttribute('data-type')||'url';element.innerHTML='';if('script'===type){let inlineTemplate=document.getElementById(template);if(inlineTemplate&&inlineTemplate.innerHTML){element.innerHTML=inlineTemplate.innerHTML;element.dispatchEvent(new CustomEvent('template-loaded',{bubbles:true,cancelable:false}));} else{element.innerHTML='Missing template "'+template+'"';} return;} -http.get(template).then(function(element){return function(data){element.innerHTML=data;view.render(element);element.dispatchEvent(new CustomEvent('template-loaded',{bubbles:true,cancelable:false}));}}(element),function(){throw new Error('Failed loading template');});}});window.ls.error=function(){return function(error){console.error("ERROR-APP",error);};};window.addEventListener("error",function(event){console.error("ERROR-EVENT:",event.error.message,event.error.stack);});document.addEventListener("account.deleteSession",function(){window.location="/auth/signin";});document.addEventListener("account.create",function(){let container=window.ls.container;let form=container.get('serviceForm');let sdk=container.get('console');let promise=sdk.account.createSession(form.email,form.password);container.set("serviceForm",{},true,true);promise.then(function(){window.location='/console';},function(error){window.location='/auth/signup?failure=1';});});(function(window){"use strict";window.ls.container.set('alerts',function(window){return{list:[],ids:0,counter:0,max:5,add:function(message,time){var scope=this;message.id=scope.ids++;scope.counter++;scope.list.unshift(message);if(scope.counter>scope.max){scope.list.pop();scope.counter--;} +http.get(template).then(function(element){return function(data){element.innerHTML=data;view.render(element);element.dispatchEvent(new CustomEvent('template-loaded',{bubbles:true,cancelable:false}));}}(element),function(){throw new Error('Failed loading template');});}});window.ls.error=function(){return function(error){console.error("ERROR-APP",error);};};window.addEventListener("error",function(event){console.error("ERROR-EVENT:",event.error.message,event.error.stack);});document.addEventListener("account.deleteSession",function(){return;window.location="/auth/signin";});document.addEventListener("account.create",function(){let container=window.ls.container;let form=container.get('serviceForm');let sdk=container.get('console');let promise=sdk.account.createSession(form.email,form.password);container.set("serviceForm",{},true,true);promise.then(function(){window.location='/console';},function(error){window.location='/auth/signup?failure=1';});});(function(window){"use strict";window.ls.container.set('alerts',function(window){return{list:[],ids:0,counter:0,max:5,add:function(message,time){var scope=this;message.id=scope.ids++;scope.counter++;scope.list.unshift(message);if(scope.counter>scope.max){scope.list.pop();scope.counter--;} if(time>0){window.setTimeout(function(message){return function(){scope.remove(message.id)}}(message),time);} return message.id;},remove:function(id){let scope=this;for(let index=0;indexelement.trim()):[];failure=failure&&failure!=""?failure.split(",").map(element=>element.trim()):[];if(debug) +element.innerHTML="";element.appendChild(child);container.set("chart",new Chart(child.getContext("2d"),config),true);element.dataset["canvas"]=true;}});(function(window){"use strict";window.ls.view.add({selector:"data-service",controller:function(element,view,container,form,alerts,expression,window){let action=element.dataset["service"];let service=element.dataset["name"]||action;let event=expression.parse(element.dataset["event"]);let confirm=element.dataset["confirm"]||"";let loading=element.dataset["loading"]||"";let loaderId=null;let scope=element.dataset["scope"]||"sdk";let debug=!!element.dataset["debug"];let success=element.dataset["success"]||"";let failure=element.dataset["failure"]||"";success=success&&success!=""?success.split(",").map(element=>element.trim()):[];failure=failure&&failure!=""?failure.split(",").map(element=>element.trim()):[];if(debug) console.log("%c[service init]: "+action+" ("+service+")","color:red");let callbacks={reset:function(){return function(){if("FORM"===element.tagName){return element.reset();} throw new Error("This callback is only valid for forms");};},alert:function(text,classname){return function(alerts){alerts.add({text:text,class:classname||"success"},3000);};},redirect:function(url){return function(router){window.location=url||"/";};},reload:function(){return function(router){router.reload();};},state:function(keys){let updateQueryString=function(key,value,url){var re=new RegExp("([?&])"+key+"=.*?(&|#|$)(.*)","gi"),hash;if(re.test(url)){if(typeof value!=="undefined"&&value!==null){return url.replace(re,"$1"+key+"="+value+"$2$3");}else{hash=url.split("#");url=hash[0].replace(re,"$1$3").replace(/(&|\?)$/,"");if(typeof hash[1]!=="undefined"&&hash[1]!==null){url+="#"+hash[1];} return url;}}else{if(typeof value!=="undefined"&&value!==null){var separator=url.indexOf("?")!==-1?"&":"?";hash=url.split("#");url=hash[0]+separator+key+"="+value;if(typeof hash[1]!=="undefined"&&hash[1]!==null){url+="#"+hash[1];}