1
0
Fork 0
mirror of synced 2024-05-13 17:22:38 +12:00

Added UI adjutments for Apple unique secret key

This commit is contained in:
Eldad Fux 2020-05-29 15:02:53 +03:00
parent d6fbd734a3
commit 7296db0d06
10 changed files with 286 additions and 67 deletions

View file

@ -104,6 +104,7 @@ $utopia->init(function () use ($utopia, $request, $response, &$user, $project, $
if(!$originValidator->isValid($origin)
&& in_array($request->getMethod(), [Request::METHOD_POST, Request::METHOD_PUT, Request::METHOD_PATCH, Request::METHOD_DELETE])
&& $route->getLabel('origin', false) !== '*'
&& empty($request->getHeader('X-Appwrite-Key', ''))) {
throw new Exception($originValidator->getDescription(), 403);
}

View file

@ -5,30 +5,35 @@ return [
'developers' => 'https://developer.atlassian.com/bitbucket',
'icon' => 'icon-bitbucket',
'enabled' => true,
'form' => false,
'mock' => false,
],
'facebook' => [
'developers' => 'https://developers.facebook.com/',
'icon' => 'icon-facebook',
'enabled' => true,
'form' => false,
'mock' => false,
],
'github' => [
'developers' => 'https://developer.github.com/',
'icon' => 'icon-github-circled',
'enabled' => true,
'form' => false,
'mock' => false,
],
'gitlab' => [
'developers' => 'https://docs.gitlab.com/ee/api/',
'icon' => 'icon-gitlab',
'enabled' => true,
'form' => false,
'mock' => false,
],
'google' => [
'developers' => 'https://developers.google.com/',
'icon' => 'icon-google',
'enabled' => true,
'form' => false,
'mock' => false,
],
// 'instagram' => [
@ -41,6 +46,7 @@ return [
'developers' => 'https://developer.microsoft.com/en-us/',
'icon' => 'icon-windows',
'enabled' => true,
'form' => false,
'mock' => false,
],
// 'twitter' => [
@ -53,90 +59,105 @@ return [
'developers' => 'https://developer.linkedin.com/',
'icon' => 'icon-linkedin',
'enabled' => true,
'form' => false,
'mock' => false,
],
'slack' => [
'developers' => 'https://api.slack.com/',
'icon' => 'icon-slack',
'enabled' => true,
'form' => false,
'mock' => false,
],
'dropbox' => [
'developers' => 'https://www.dropbox.com/developers/documentation',
'icon' => 'icon-dropbox',
'enabled' => true,
'form' => false,
'mock' => false,
],
'salesforce' => [
'developers' => 'https://developer.salesforce.com/docs/',
'icon' => 'icon-salesforce',
'enabled' => true,
'form' => false,
'mock' => false,
],
'apple' => [
'developers' => 'https://developer.apple.com/',
'icon' => 'icon-apple',
'enabled' => true,
'form' => 'apple.phtml',
'mock' => false,
],
'amazon' => [
'developers' => 'https://developer.amazon.com/apps-and-games/services-and-apis',
'icon' => 'icon-amazon',
'enabled' => true,
'form' => false,
'mock' => false,
],
'vk' => [
'developers' => 'https://vk.com/dev',
'icon' => 'icon-vk',
'enabled' => true,
'form' => false,
'mock' => false,
],
'discord' => [
'developers' => 'https://discordapp.com/developers/docs/topics/oauth2',
'icon' => 'icon-discord',
'enabled' => true,
'form' => false,
'mock' => false,
],
'twitch' => [
'developers' => 'https://dev.twitch.tv/docs/authentication',
'icon' => 'icon-twitch',
'enabled' => true,
'form' => false,
'mock' => false,
],
'spotify' => [
'developers' => 'https://developer.spotify.com/documentation/general/guides/authorization-guide/',
'icon' => 'icon-spotify',
'enabled' => true,
'form' => false,
'mock' => false,
],
'yahoo' => [
'developers' => 'https://developer.yahoo.com/oauth2/guide/flows_authcode/',
'icon' => 'icon-yahoo',
'enabled' => true,
'form' => false,
'mock' => false,
],
'yandex' => [
'developers' => 'https://tech.yandex.com/oauth/',
'icon' => 'icon-yandex',
'enabled' => true,
'form' => false,
'mock' => false,
],
'twitter' => [
'developers' => 'https://developer.twitter.com/',
'icon' => 'icon-twitter',
'enabled' => false,
'form' => false,
'mock' => false
],
'paypal' => [
'developers' => 'https://developer.paypal.com/docs/api/overview/',
'icon' => 'icon-paypal',
'enabled' => true,
'form' => false,
'mock' => false
],
'bitly' => [
'developers' => 'https://dev.bitly.com/v4_documentation.html',
'icon' => 'icon-bitly',
'enabled' => true,
'form' => false,
'mock' => false
],
// Keep Last

View file

@ -308,6 +308,29 @@ $utopia->get('/v1/account/sessions/oauth2/callback/:provider/:projectId')
}
);
$utopia->post('/v1/account/sessions/oauth2/callback/:provider/:projectId')
->desc('OAuth2 Callback')
->label('error', __DIR__.'/../../views/general/error.phtml')
->label('scope', 'public')
->label('origin', '*')
->label('docs', false)
->param('projectId', '', function () { return new Text(1024); }, 'Project unique ID.')
->param('provider', '', function () { return new WhiteList(array_keys(Config::getParam('providers'))); }, 'OAuth2 provider.')
->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) {
$domain = Config::getParam('domain');
$protocol = Config::getParam('protocol');
$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]));
}
);
$utopia->get('/v1/account/sessions/oauth2/:provider/redirect')
->desc('OAuth2 Redirect')
->label('error', __DIR__.'/../../views/general/error.phtml')
@ -361,6 +384,7 @@ $utopia->get('/v1/account/sessions/oauth2/:provider/redirect')
if (!empty($state['failure']) && !$validateURL->isValid($state['failure'])) {
throw new Exception('Invalid redirect URL for failure login', 400);
}
$state['failure'] = null;
$accessToken = $oauth2->getAccessToken($code);

View file

@ -361,7 +361,7 @@ $utopia->patch('/v1/projects/:projectId/oauth2')
->param('projectId', '', function () { return new UID(); }, 'Project unique ID.')
->param('provider', '', function () { return new WhiteList(array_keys(Config::getParam('providers'))); }, 'Provider Name', false)
->param('appId', '', function () { return new Text(256); }, 'Provider app ID.', true)
->param('secret', '', function () { return new text(256); }, 'Provider secret key.', true)
->param('secret', '', function () { return new text(512); }, 'Provider secret key.', true)
->action(
function ($projectId, $provider, $appId, $secret) use ($request, $response, $consoleDB) {
$project = $consoleDB->getDocument($projectId);

View file

@ -337,12 +337,13 @@ $providers = $this->getParam('providers', []);
<?php foreach ($providers as $provider => $data):
if (isset($data['enabled']) && !$data['enabled']) { continue; }
if (isset($data['mock']) && $data['mock']) { continue; }
$form = (isset($data['form'])) ? $data['form'] : false;
?>
<li class="clear <?php echo (isset($data['enabled']) && !$data['enabled']) ? 'dev-feature' : ''; ?>">
<div data-ui-modal class="modal close" data-button-alias="none" data-open-event="provider-update-<?php echo $provider; ?>">
<button type="button" class="close pull-end" data-ui-modal-close=""><i class="icon-cancel"></i></button>
<h1><?php echo ucfirst($provider); ?> OAuth2 Settings</h1>
<h1><?php echo $this->escape(ucfirst($provider)); ?> OAuth2 Settings</h1>
<form
data-analytics-event="submit"
@ -358,23 +359,30 @@ $providers = $this->getParam('providers', []);
data-failure="alert"
data-failure-param-alert-text="Failed to update project OAuth2 settings"
data-failure-param-alert-classname="error">
<input name="provider" id="provider<?php echo ucfirst($provider); ?>" type="hidden" autocomplete="off" value="<?php echo $provider; ?>">
<input name="provider" id="provider<?php echo $this->escape(ucfirst($provider)); ?>" type="hidden" autocomplete="off" value="<?php echo $this->escape($provider); ?>">
<label for="oauth2<?php echo ucfirst($provider); ?>Appid">App ID</label>
<input name="appId" id="oauth2<?php echo ucfirst($provider); ?>Appid" type="text" autocomplete="off" data-ls-bind="{{console-project.usersOauth2<?php echo ucfirst($provider); ?>Appid}}">
<?php if(!$form): ?>
<label for="oauth2<?php echo $this->escape(ucfirst($provider)); ?>Appid">App ID</label>
<input name="appId" id="oauth2<?php echo $this->escape(ucfirst($provider)); ?>Appid" type="text" autocomplete="off" data-ls-bind="{{console-project.usersOauth2<?php echo $this->escape(ucfirst($provider)); ?>Appid}}">
<label for="oauth2<?php echo $this->escape(ucfirst($provider)); ?>Secret">App Secret</label>
<input name="secret" id="oauth2<?php echo $this->escape(ucfirst($provider)); ?>Secret" type="text" autocomplete="off" data-ls-bind="{{console-project.usersOauth2<?php echo $this->escape(ucfirst($provider)); ?>Secret}}">
<?php else: ?>
<label for="oauth2<?php echo $this->escape(ucfirst($provider)); ?>Appid">Bundle ID <span class="tooltip" data-tooltip="Attribute internal display name"><i class="icon-info-circled"></i></span></label>
<input name="appId" id="oauth2<?php echo $this->escape(ucfirst($provider)); ?>Appid" type="text" autocomplete="off" data-ls-bind="{{console-project.usersOauth2<?php echo $this->escape(ucfirst($provider)); ?>Appid}}" placeholder="com.company.appname" />
<label for="oauth2<?php echo ucfirst($provider); ?>Secret">App Secret</label>
<input name="secret" id="oauth2<?php echo ucfirst($provider); ?>Secret" type="text" autocomplete="off" data-ls-bind="{{console-project.usersOauth2<?php echo ucfirst($provider); ?>Secret}}">
<input name="secret" data-forms-oauth-apple id="oauth2<?php echo $this->escape(ucfirst($provider)); ?>Appid" type="hidden" autocomplete="off" data-ls-bind="{{console-project.usersOauth2<?php echo $this->escape(ucfirst($provider)); ?>Secret}}" />
<?php endif; ?>
<div class="info row thin margin-bottom margin-top">
<div class="col span-1">
<i class="icon-info-circled text-sign"></i>
</div>
<div class="col span-11">
<p>To complete set up, add this OAuth2 redirect URI to your <?php echo ucfirst($provider); ?> app configuration.</p>
<p>To complete set up, add this OAuth2 redirect URI to your <?php echo $this->escape(ucfirst($provider)); ?> app configuration.</p>
<div class="input-copy">
<input data-forms-copy type="text" disabled data-ls-bind="{{env.PROTOCOL}}://{{env.DOMAIN}}/v1/account/sessions/oauth2/callback/<?php echo $provider; ?>/{{router.params.project}}" class="margin-bottom-no" />
<input data-forms-copy type="text" disabled data-ls-bind="{{env.PROTOCOL}}://{{env.DOMAIN}}/v1/account/sessions/oauth2/callback/<?php echo $this->escape($provider); ?>/{{router.params.project}}" class="margin-bottom-no" />
</div>
</div>
</div>
@ -384,17 +392,17 @@ $providers = $this->getParam('providers', []);
</div>
<span data-ls-if="
{{console-project.usersOauth2<?php echo ucfirst($provider); ?>Appid}} &&
{{console-project.usersOauth2<?php echo ucfirst($provider); ?>Secret}}">
{{console-project.usersOauth2<?php echo $this->escape(ucfirst($provider)); ?>Appid}} &&
{{console-project.usersOauth2<?php echo $this->escape(ucfirst($provider)); ?>Secret}}">
<button class="switch on pull-end margin-top-small" data-ls-ui-trigger="provider-update-<?php echo $provider; ?>"></button>
</span>
<span data-ls-if="
!{{console-project.usersOauth2<?php echo ucfirst($provider); ?>Appid}} ||
!{{console-project.usersOauth2<?php echo ucfirst($provider); ?>Secret}}">
<button class="switch pull-end margin-top-small" data-ls-ui-trigger="provider-update-<?php echo $provider; ?>"></button>
!{{console-project.usersOauth2<?php echo $this->escape(ucfirst($provider)); ?>Appid}} ||
!{{console-project.usersOauth2<?php echo $this->escape(ucfirst($provider)); ?>Secret}}">
<button class="switch pull-end margin-top-small" data-ls-ui-trigger="provider-update-<?php echo $this->escape($provider); ?>"></button>
</span>
<img src="/images/oauth2/<?php echo strtolower($provider); ?>.png?buster=<?php echo APP_CACHE_BUSTER; ?>" alt="<?php echo ucfirst($provider); ?> Logo" class="pull-start provider margin-end" />
<img src="/images/oauth2/<?php echo $this->escape(strtolower($provider)); ?>.png?buster=<?php echo APP_CACHE_BUSTER; ?>" alt="<?php echo $this->escape(ucfirst($provider)); ?> Logo" class="pull-start provider margin-end" />
<span>
<?php echo ucfirst($provider); ?>
</span>

View file

@ -47,6 +47,7 @@ const configApp = {
'public/scripts/views/forms/move-down.js',
'public/scripts/views/forms/move-up.js',
'public/scripts/views/forms/nav.js',
'public/scripts/views/forms/oauth-apple.js',
'public/scripts/views/forms/password-meter.js',
'public/scripts/views/forms/pell.js',
'public/scripts/views/forms/remove.js',

View file

@ -2568,7 +2568,7 @@ return"< 1s";}).add("markdown",function($value,markdown){return markdown.render(
let thresh=1000;if(Math.abs($value)<thresh){return $value+" B";}
let units=["kB","MB","GB","TB","PB","EB","ZB","YB"];let u=-1;do{$value/=thresh;++u;}while(Math.abs($value)>=thresh&&u<units.length-1);return($value.toFixed(1)+'<span class="text-size-small unit">'+
units[u]+"</span>");}).add("statsTotal",function($value){if(!$value){return 0;}
$value=abbreviate($value,1,false,false);return $value==="0"?"N/A":$value;}).add("isEmpty",function($value){return(!!$value);}).add("isEmptyObject",function($value){return((Object.keys($value).length===0&&$value.constructor===Object)||$value.length===0)}).add("activeDomainsCount",function($value){let result=[];if(Array.isArray($value)){result=$value.filter(function(node){return(node.verification&&node.certificateId);});}
$value=abbreviate($value,0,false,false);return $value==="0"?"N/A":$value;}).add("isEmpty",function($value){return(!!$value);}).add("isEmptyObject",function($value){return((Object.keys($value).length===0&&$value.constructor===Object)||$value.length===0)}).add("activeDomainsCount",function($value){let result=[];if(Array.isArray($value)){result=$value.filter(function(node){return(node.verification&&node.certificateId);});}
return result.length;}).add("documentAction",function(container){let collection=container.get('project-collection');let document=container.get('project-document');if(collection&&document&&!document.$id){return'database.createDocument';}
return'database.updateDocument';}).add("documentSuccess",function(container){let document=container.get('project-document');if(document&&!document.$id){return',redirect';}
return'';}).add("firstElement",function($value){if($value&&$value[0]){return $value[0];}
@ -2640,7 +2640,11 @@ list["filters-"+filter.key]=params[key][i];}}}}
return list;};let apply=function(params){let cached=container.get(name);cached=cached?cached.params:[];params=Object.assign(cached,params);container.set(name,{name:name,params:params,query:serialize(params),forward:parseInt(params.offset)+parseInt(params.limit),backward:parseInt(params.offset)-parseInt(params.limit),keys:flatten(params)},true,name);document.dispatchEvent(new CustomEvent(name+"-changed",{bubbles:false,cancelable:true}));};switch(element.tagName){case"INPUT":break;case"TEXTAREA":break;case"BUTTON":element.addEventListener("click",function(){apply(JSON.parse(expression.parse(element.dataset["params"]||"{}")));});break;case"FORM":element.addEventListener("input",function(){apply(form.toJson(element));});element.addEventListener("change",function(){apply(form.toJson(element));});element.addEventListener("reset",function(){setTimeout(function(){apply(form.toJson(element));},0);});events=events.trim().split(",");for(let y=0;y<events.length;y++){if(events[y]==="init"){element.addEventListener("rendered",function(){apply(form.toJson(element));},{once:true});}else{}
element.setAttribute("data-event","none");}
break;default:break;}}});})(window);(function(window){"use strict";window.ls.container.get("view").add({selector:"data-forms-move-down",controller:function(element){Array.prototype.slice.call(element.querySelectorAll("[data-move-down]")).map(function(obj){obj.addEventListener("click",function(){if(element.nextElementSibling){element.parentNode.insertBefore(element.nextElementSibling,element);element.scrollIntoView(true);}});});}});})(window);(function(window){"use strict";window.ls.container.get("view").add({selector:"data-forms-move-up",controller:function(element){Array.prototype.slice.call(element.querySelectorAll("[data-move-up]")).map(function(obj){obj.addEventListener("click",function(){if(element.previousElementSibling){element.parentNode.insertBefore(element,element.previousElementSibling);element.scrollIntoView(true);}});});}});})(window);(function(window){"use strict";window.ls.container.get("view").add({selector:"data-forms-nav",repeat:false,controller:function(element,view,container,document){let titles=document.querySelectorAll('[data-forms-nav-anchor]');let links=element.querySelectorAll('[data-forms-nav-link]');let minLink=null;let check=function(){let minDistance=null;let minElement=null;for(let i=0;i<titles.length;++i){let title=titles[i];let distance=title.getBoundingClientRect().top;console.log(i);if((minDistance===null||minDistance>=distance)&&(distance>=0)){if(minLink){minLink.classList.remove('selected');}
console.log('old',minLink);minDistance=distance;minElement=title;minLink=links[i];minLink.classList.add('selected');console.log('new',minLink);}}};window.addEventListener('scroll',check);check();}});})(window);(function(window){"use strict";window.ls.container.get("view").add({selector:"data-forms-password-meter",controller:function(element,window){var calc=function(password){var score=0;if(!password)return score;var letters=new window.Object();for(var i=0;i<password.length;i++){letters[password[i]]=(letters[password[i]]||0)+1;score+=5.0/letters[password[i]];}
console.log('old',minLink);minDistance=distance;minElement=title;minLink=links[i];minLink.classList.add('selected');console.log('new',minLink);}}};window.addEventListener('scroll',check);check();}});})(window);(function(window){"use strict";window.ls.container.get("view").add({selector:"data-forms-oauth-apple",controller:function(element){let container=document.createElement("div");let row=document.createElement("div");let col1=document.createElement("div");let col2=document.createElement("div");let keyID=document.createElement("input");let keyLabel=document.createElement("label");let teamID=document.createElement("input");let teamLabel=document.createElement("label");let p8=document.createElement("textarea");let p8Label=document.createElement("label");keyLabel.textContent='Key ID';teamLabel.textContent='Team ID';p8Label.textContent='P8 File';row.classList.add('row');row.classList.add('thin');container.appendChild(row);container.appendChild(p8Label);container.appendChild(p8);row.appendChild(col1);row.appendChild(col2);col1.classList.add('col');col1.classList.add('span-6');col1.appendChild(keyLabel);col1.appendChild(keyID);col2.classList.add('col');col2.classList.add('span-6');col2.appendChild(teamLabel);col2.appendChild(teamID);keyID.type='text';keyID.placeholder='SHAB13ROFN';teamID.type='text';teamID.placeholder='ELA2CD3AED';p8.accept='.p8';p8.classList.add('margin-bottom-no');element.parentNode.insertBefore(container,element.nextSibling);element.addEventListener('change',sync);keyID.addEventListener('change',update);teamID.addEventListener('change',update);p8.addEventListener('change',update);function update(){let json={};json.keyID=keyID.value;json.teamID=teamID.value;json.p8=p8.value;element.value=JSON.stringify(json);}
function sync(){console.log('sync');if(!element.value){return;}
let json={};try{json=JSON.parse(element.value);}catch(error){console.error('Failed to parse secret key');}
teamID.value=json.teamID||'';keyID.value=json.keyID||'';p8.value=json.p8||'';}
sync();}});})(window);(function(window){"use strict";window.ls.container.get("view").add({selector:"data-forms-password-meter",controller:function(element,window){var calc=function(password){var score=0;if(!password)return score;var letters=new window.Object();for(var i=0;i<password.length;i++){letters[password[i]]=(letters[password[i]]||0)+1;score+=5.0/letters[password[i]];}
var variations={digits:/\d/.test(password),lower:/[a-z]/.test(password),upper:/[A-Z]/.test(password),nonWords:/\W/.test(password)};var variationCount=0;for(var check in variations){if(variations.hasOwnProperty(check)){variationCount+=variations[check]===true?1:0;}}
score+=(variationCount-1)*10;return parseInt(score);};var callback=function(){var score=calc(this.value);if(""===this.value)return(meter.className="password-meter");if(score>60)return(meter.className="password-meter strong");if(score>30)return(meter.className="password-meter medium");if(score>=0)return(meter.className="password-meter weak");};var meter=window.document.createElement("div");meter.className="password-meter";element.parentNode.insertBefore(meter,element.nextSibling);element.addEventListener("change",callback);element.addEventListener("keypress",callback);element.addEventListener("keyup",callback);element.addEventListener("keydown",callback);}});})(window);(function(window){"use strict";window.ls.container.get("view").add({selector:"data-forms-pell",controller:function(element,window,document,markdown,rtl){var div=document.createElement("div");element.className="pell hide";div.className="input pell";element.parentNode.insertBefore(div,element);element.tabIndex=-1;var turndownService=new TurndownService();turndownService.addRule("underline",{filter:["u"],replacement:function(content){return"__"+content+"__";}});var editor=window.pell.init({element:div,onChange:function onChange(html){alignText();element.value=turndownService.turndown(html);},defaultParagraphSeparator:"p",actions:[{name:"bold",icon:'<i class="icon-bold"></i>'},{name:"underline",icon:'<i class="icon-underline"></i>'},{name:"italic",icon:'<i class="icon-italic"></i>'},{name:"olist",icon:'<i class="icon-list-numbered"></i>'},{name:"ulist",icon:'<i class="icon-list-bullet"></i>'},{name:"link",icon:'<i class="icon-link"></i>'}]});var clean=function(e){e.stopPropagation();e.preventDefault();var clipboardData=e.clipboardData||window.clipboardData;console.log(clipboardData.getData("Text"));window.pell.exec("insertText",clipboardData.getData("Text"));return true;};var alignText=function(){let paragraphs=editor.content.querySelectorAll('p,li');let last='';for(let paragraph of paragraphs){var content=paragraph.textContent;if(content.trim()===''){content=last.textContent;}
if(rtl.isRTL(content)){paragraph.style.direction='rtl';paragraph.style.textAlign='right';}

View file

@ -284,7 +284,7 @@ return"< 1s";}).add("markdown",function($value,markdown){return markdown.render(
let thresh=1000;if(Math.abs($value)<thresh){return $value+" B";}
let units=["kB","MB","GB","TB","PB","EB","ZB","YB"];let u=-1;do{$value/=thresh;++u;}while(Math.abs($value)>=thresh&&u<units.length-1);return($value.toFixed(1)+'<span class="text-size-small unit">'+
units[u]+"</span>");}).add("statsTotal",function($value){if(!$value){return 0;}
$value=abbreviate($value,1,false,false);return $value==="0"?"N/A":$value;}).add("isEmpty",function($value){return(!!$value);}).add("isEmptyObject",function($value){return((Object.keys($value).length===0&&$value.constructor===Object)||$value.length===0)}).add("activeDomainsCount",function($value){let result=[];if(Array.isArray($value)){result=$value.filter(function(node){return(node.verification&&node.certificateId);});}
$value=abbreviate($value,0,false,false);return $value==="0"?"N/A":$value;}).add("isEmpty",function($value){return(!!$value);}).add("isEmptyObject",function($value){return((Object.keys($value).length===0&&$value.constructor===Object)||$value.length===0)}).add("activeDomainsCount",function($value){let result=[];if(Array.isArray($value)){result=$value.filter(function(node){return(node.verification&&node.certificateId);});}
return result.length;}).add("documentAction",function(container){let collection=container.get('project-collection');let document=container.get('project-document');if(collection&&document&&!document.$id){return'database.createDocument';}
return'database.updateDocument';}).add("documentSuccess",function(container){let document=container.get('project-document');if(document&&!document.$id){return',redirect';}
return'';}).add("firstElement",function($value){if($value&&$value[0]){return $value[0];}
@ -356,7 +356,11 @@ list["filters-"+filter.key]=params[key][i];}}}}
return list;};let apply=function(params){let cached=container.get(name);cached=cached?cached.params:[];params=Object.assign(cached,params);container.set(name,{name:name,params:params,query:serialize(params),forward:parseInt(params.offset)+parseInt(params.limit),backward:parseInt(params.offset)-parseInt(params.limit),keys:flatten(params)},true,name);document.dispatchEvent(new CustomEvent(name+"-changed",{bubbles:false,cancelable:true}));};switch(element.tagName){case"INPUT":break;case"TEXTAREA":break;case"BUTTON":element.addEventListener("click",function(){apply(JSON.parse(expression.parse(element.dataset["params"]||"{}")));});break;case"FORM":element.addEventListener("input",function(){apply(form.toJson(element));});element.addEventListener("change",function(){apply(form.toJson(element));});element.addEventListener("reset",function(){setTimeout(function(){apply(form.toJson(element));},0);});events=events.trim().split(",");for(let y=0;y<events.length;y++){if(events[y]==="init"){element.addEventListener("rendered",function(){apply(form.toJson(element));},{once:true});}else{}
element.setAttribute("data-event","none");}
break;default:break;}}});})(window);(function(window){"use strict";window.ls.container.get("view").add({selector:"data-forms-move-down",controller:function(element){Array.prototype.slice.call(element.querySelectorAll("[data-move-down]")).map(function(obj){obj.addEventListener("click",function(){if(element.nextElementSibling){element.parentNode.insertBefore(element.nextElementSibling,element);element.scrollIntoView(true);}});});}});})(window);(function(window){"use strict";window.ls.container.get("view").add({selector:"data-forms-move-up",controller:function(element){Array.prototype.slice.call(element.querySelectorAll("[data-move-up]")).map(function(obj){obj.addEventListener("click",function(){if(element.previousElementSibling){element.parentNode.insertBefore(element,element.previousElementSibling);element.scrollIntoView(true);}});});}});})(window);(function(window){"use strict";window.ls.container.get("view").add({selector:"data-forms-nav",repeat:false,controller:function(element,view,container,document){let titles=document.querySelectorAll('[data-forms-nav-anchor]');let links=element.querySelectorAll('[data-forms-nav-link]');let minLink=null;let check=function(){let minDistance=null;let minElement=null;for(let i=0;i<titles.length;++i){let title=titles[i];let distance=title.getBoundingClientRect().top;console.log(i);if((minDistance===null||minDistance>=distance)&&(distance>=0)){if(minLink){minLink.classList.remove('selected');}
console.log('old',minLink);minDistance=distance;minElement=title;minLink=links[i];minLink.classList.add('selected');console.log('new',minLink);}}};window.addEventListener('scroll',check);check();}});})(window);(function(window){"use strict";window.ls.container.get("view").add({selector:"data-forms-password-meter",controller:function(element,window){var calc=function(password){var score=0;if(!password)return score;var letters=new window.Object();for(var i=0;i<password.length;i++){letters[password[i]]=(letters[password[i]]||0)+1;score+=5.0/letters[password[i]];}
console.log('old',minLink);minDistance=distance;minElement=title;minLink=links[i];minLink.classList.add('selected');console.log('new',minLink);}}};window.addEventListener('scroll',check);check();}});})(window);(function(window){"use strict";window.ls.container.get("view").add({selector:"data-forms-oauth-apple",controller:function(element){let container=document.createElement("div");let row=document.createElement("div");let col1=document.createElement("div");let col2=document.createElement("div");let keyID=document.createElement("input");let keyLabel=document.createElement("label");let teamID=document.createElement("input");let teamLabel=document.createElement("label");let p8=document.createElement("textarea");let p8Label=document.createElement("label");keyLabel.textContent='Key ID';teamLabel.textContent='Team ID';p8Label.textContent='P8 File';row.classList.add('row');row.classList.add('thin');container.appendChild(row);container.appendChild(p8Label);container.appendChild(p8);row.appendChild(col1);row.appendChild(col2);col1.classList.add('col');col1.classList.add('span-6');col1.appendChild(keyLabel);col1.appendChild(keyID);col2.classList.add('col');col2.classList.add('span-6');col2.appendChild(teamLabel);col2.appendChild(teamID);keyID.type='text';keyID.placeholder='SHAB13ROFN';teamID.type='text';teamID.placeholder='ELA2CD3AED';p8.accept='.p8';p8.classList.add('margin-bottom-no');element.parentNode.insertBefore(container,element.nextSibling);element.addEventListener('change',sync);keyID.addEventListener('change',update);teamID.addEventListener('change',update);p8.addEventListener('change',update);function update(){let json={};json.keyID=keyID.value;json.teamID=teamID.value;json.p8=p8.value;element.value=JSON.stringify(json);}
function sync(){console.log('sync');if(!element.value){return;}
let json={};try{json=JSON.parse(element.value);}catch(error){console.error('Failed to parse secret key');}
teamID.value=json.teamID||'';keyID.value=json.keyID||'';p8.value=json.p8||'';}
sync();}});})(window);(function(window){"use strict";window.ls.container.get("view").add({selector:"data-forms-password-meter",controller:function(element,window){var calc=function(password){var score=0;if(!password)return score;var letters=new window.Object();for(var i=0;i<password.length;i++){letters[password[i]]=(letters[password[i]]||0)+1;score+=5.0/letters[password[i]];}
var variations={digits:/\d/.test(password),lower:/[a-z]/.test(password),upper:/[A-Z]/.test(password),nonWords:/\W/.test(password)};var variationCount=0;for(var check in variations){if(variations.hasOwnProperty(check)){variationCount+=variations[check]===true?1:0;}}
score+=(variationCount-1)*10;return parseInt(score);};var callback=function(){var score=calc(this.value);if(""===this.value)return(meter.className="password-meter");if(score>60)return(meter.className="password-meter strong");if(score>30)return(meter.className="password-meter medium");if(score>=0)return(meter.className="password-meter weak");};var meter=window.document.createElement("div");meter.className="password-meter";element.parentNode.insertBefore(meter,element.nextSibling);element.addEventListener("change",callback);element.addEventListener("keypress",callback);element.addEventListener("keyup",callback);element.addEventListener("keydown",callback);}});})(window);(function(window){"use strict";window.ls.container.get("view").add({selector:"data-forms-pell",controller:function(element,window,document,markdown,rtl){var div=document.createElement("div");element.className="pell hide";div.className="input pell";element.parentNode.insertBefore(div,element);element.tabIndex=-1;var turndownService=new TurndownService();turndownService.addRule("underline",{filter:["u"],replacement:function(content){return"__"+content+"__";}});var editor=window.pell.init({element:div,onChange:function onChange(html){alignText();element.value=turndownService.turndown(html);},defaultParagraphSeparator:"p",actions:[{name:"bold",icon:'<i class="icon-bold"></i>'},{name:"underline",icon:'<i class="icon-underline"></i>'},{name:"italic",icon:'<i class="icon-italic"></i>'},{name:"olist",icon:'<i class="icon-list-numbered"></i>'},{name:"ulist",icon:'<i class="icon-list-bullet"></i>'},{name:"link",icon:'<i class="icon-link"></i>'}]});var clean=function(e){e.stopPropagation();e.preventDefault();var clipboardData=e.clipboardData||window.clipboardData;console.log(clipboardData.getData("Text"));window.pell.exec("insertText",clipboardData.getData("Text"));return true;};var alignText=function(){let paragraphs=editor.content.querySelectorAll('p,li');let last='';for(let paragraph of paragraphs){var content=paragraph.textContent;if(content.trim()===''){content=last.textContent;}
if(rtl.isRTL(content)){paragraph.style.direction='rtl';paragraph.style.textAlign='right';}

View file

@ -0,0 +1,94 @@
(function(window) {
"use strict";
window.ls.container.get("view").add({
selector: "data-forms-oauth-apple",
controller: function(element) {
let container = document.createElement("div");
let row = document.createElement("div");
let col1 = document.createElement("div");
let col2 = document.createElement("div");
let keyID = document.createElement("input");
let keyLabel = document.createElement("label");
let teamID = document.createElement("input");
let teamLabel = document.createElement("label");
let p8 = document.createElement("textarea");
let p8Label = document.createElement("label");
keyLabel.textContent = 'Key ID';
teamLabel.textContent = 'Team ID';
p8Label.textContent = 'P8 File';
row.classList.add('row');
row.classList.add('thin');
container.appendChild(row);
container.appendChild(p8Label);
container.appendChild(p8);
row.appendChild(col1);
row.appendChild(col2);
col1.classList.add('col');
col1.classList.add('span-6');
col1.appendChild(keyLabel);
col1.appendChild(keyID);
col2.classList.add('col');
col2.classList.add('span-6');
col2.appendChild(teamLabel);
col2.appendChild(teamID);
keyID.type = 'text';
keyID.placeholder = 'SHAB13ROFN';
teamID.type = 'text';
teamID.placeholder = 'ELA2CD3AED';
p8.accept = '.p8';
p8.classList.add('margin-bottom-no');
element.parentNode.insertBefore(container, element.nextSibling);
element.addEventListener('change', sync);
keyID.addEventListener('change', update);
teamID.addEventListener('change', update);
p8.addEventListener('change', update);
function update() {
let json = {};
json.keyID = keyID.value;
json.teamID = teamID.value;
json.p8 = p8.value;
element.value = JSON.stringify(json);
}
function sync() {
console.log('sync');
if(!element.value) {
return;
}
let json = {};
try {
json = JSON.parse(element.value);
} catch (error) {
console.error('Failed to parse secret key');
}
teamID.value = json.teamID || '';
keyID.value = json.keyID || '';
p8.value = json.p8 || '';
}
// function syncB() {
// picker.value = element.value;
// }
// element.parentNode.insertBefore(preview, element);
// update();
sync();
}
});
})(window);

View file

@ -3,6 +3,7 @@
namespace Appwrite\Auth\OAuth2;
use Appwrite\Auth\OAuth2;
use Exception;
// Reference Material
// https://developer.okta.com/blog/2019/06/04/what-the-heck-is-sign-in-with-apple
@ -22,6 +23,11 @@ class Apple extends OAuth2
"email"
];
/**
* @var array
*/
protected $claims = [];
/**
* @return string
*/
@ -58,15 +64,18 @@ class Apple extends OAuth2
'https://appleid.apple.com/auth/token',
$headers,
http_build_query([
'grant_type' => 'authorization_code',
'code' => $code,
'client_id' => $this->appID,
'client_secret' => $this->appSecret,
'client_secret' => $this->getAppSecret(),
'redirect_uri' => $this->callback,
'grant_type' => 'authorization_code'
])
);
$accessToken = json_decode($accessToken, true);
$accessToken = json_decode($accessToken, true);
$this->claims = (isset($accessToken['id_token'])) ? explode('.', $accessToken['id_token']) : [0 => '', 1 => ''];
$this->claims = (isset($this->claims[1])) ? json_decode(base64_decode($this->claims[1]), true) : [];
if (isset($accessToken['access_token'])) {
return $accessToken['access_token'];
@ -82,10 +91,8 @@ class Apple extends OAuth2
*/
public function getUserID(string $accessToken): string
{
$user = $this->getUser($accessToken);
if (isset($user['account_id'])) {
return $user['account_id'];
if (isset($this->claims['sub']) && !empty($this->claims['sub'])) {
return $this->claims['sub'];
}
return '';
@ -98,10 +105,11 @@ class Apple extends OAuth2
*/
public function getUserEmail(string $accessToken): string
{
$user = $this->getUser($accessToken);
if (isset($user['email'])) {
return $user['email'];
if (isset($this->claims['email']) &&
!empty($this->claims['email']) &&
isset($this->claims['email_verified']) &&
$this->claims['email_verified'] === 'true') {
return $this->claims['email'];
}
return '';
@ -114,57 +122,111 @@ class Apple extends OAuth2
*/
public function getUserName(string $accessToken): string
{
$user = $this->getUser($accessToken);
if (isset($user['name'])) {
return $user['name']['display_name'];
if (isset($this->claims['email']) &&
!empty($this->claims['email']) &&
isset($this->claims['email_verified']) &&
$this->claims['email_verified'] === 'true') {
return $this->claims['email'];
}
return '';
}
/**
* @param string $accessToken
*
* @return array
*/
protected function getUser(string $accessToken): array
protected function getAppSecret():string
{
if (empty($this->user)) {
$headers[] = 'Authorization: Bearer '. urlencode($accessToken);
$user = $this->request('POST', '', $headers);
$this->user = json_decode($user, true);
try {
$secret = json_decode($this->appSecret, true);
} catch (\Throwable $th) {
throw new Exception('Invalid secret');
}
return $this->user;
}
$keyfile = (isset($secret['p8'])) ? $secret['p8'] : ''; // Your p8 Key file
$keyID = (isset($secret['keyID'])) ? $secret['keyID'] : ''; // Your Key ID
$teamID = (isset($secret['teamID'])) ? $secret['teamID'] : ''; // Your Team ID (see Developer Portal)
$bundleID = $this->appID; // Your Bundle ID
protected function getToken($p8)
{
$keyfile = 'AuthKey_AABBCC1234.p8'; # <- Your AuthKey file
$keyid = '4LFF7TZ6Q5'; # <- Your Key ID
$teamid = 'YJHMCSNREU'; # <- Your Team ID (see Developer Portal)
$bundleid = 'test2.appwrite.io'; # <- Your Bundle ID
$url = 'https://api.development.push.apple.com'; # <- development url, or use http://api.push.apple.com for production environment
$token = 'e2c48ed32ef9b018........'; # <- Device Token
$headers = [
'alg' => 'ES256',
'kid' => $keyID,
];
function base64($data) {
return rtrim(strtr(base64_encode(json_encode($data)), '+/', '-_'), '=');
}
$claims = [
'iss' => $teamID,
'iat' => time(),
'exp' => time() + 86400*180,
'aud' => 'https://appleid.apple.com',
'sub' => $bundleID,
];
$message = '{"aps":{"alert":"Hi there!","sound":"default"}}';
$pkey = openssl_pkey_get_private($keyfile);
$key = openssl_pkey_get_private('file://'.$keyfile);
$header = ['alg'=>'ES256', 'kid'=>$keyid];
$claims = ['iss'=>$teamid, 'iat'=>time()];
$header_encoded = base64($header);
$claims_encoded = base64($claims);
$payload = $this->encode(json_encode($headers)).'.'.$this->encode(json_encode($claims));
$signature = '';
openssl_sign($header_encoded . '.' . $claims_encoded, $signature, $key, 'sha256');
$jwt = $header_encoded . '.' . $claims_encoded . '.' . base64_encode($signature);
$success = openssl_sign($payload, $signature, $pkey, OPENSSL_ALGO_SHA256);
if (!$success) return '';
return $payload.'.'.$this->encode($this->fromDER($signature, 64));
}
/**
* @param string $data
*/
protected function encode($data)
{
return str_replace(['+', '/', '='], ['-', '_', ''], base64_encode($data));
}
/**
* @param string $data
*/
protected function retrievePositiveInteger(string $data): string
{
while ('00' === mb_substr($data, 0, 2, '8bit') && mb_substr($data, 2, 2, '8bit') > '7f') {
$data = mb_substr($data, 2, null, '8bit');
}
return $data;
}
/**
* @param string $der
* @param int $partLength
*/
protected function fromDER(string $der, int $partLength):string
{
$hex = \unpack('H*', $der)[1];
if ('30' !== \mb_substr($hex, 0, 2, '8bit')) { // SEQUENCE
throw new \RuntimeException();
}
if ('81' === \mb_substr($hex, 2, 2, '8bit')) { // LENGTH > 128
$hex = \mb_substr($hex, 6, null, '8bit');
}
else {
$hex = \mb_substr($hex, 4, null, '8bit');
}
if ('02' !== \mb_substr($hex, 0, 2, '8bit')) { // INTEGER
throw new \RuntimeException();
}
$Rl = \hexdec(\mb_substr($hex, 2, 2, '8bit'));
$R = $this->retrievePositiveInteger(\mb_substr($hex, 4, $Rl * 2, '8bit'));
$R = \str_pad($R, $partLength, '0', STR_PAD_LEFT);
$hex = \mb_substr($hex, 4 + $Rl * 2, null, '8bit');
if ('02' !== \mb_substr($hex, 0, 2, '8bit')) { // INTEGER
throw new \RuntimeException();
}
$Sl = \hexdec(\mb_substr($hex, 2, 2, '8bit'));
$S = $this->retrievePositiveInteger(\mb_substr($hex, 4, $Sl * 2, '8bit'));
$S = \str_pad($S, $partLength, '0', STR_PAD_LEFT);
return \pack('H*', $R.$S);
}
}