Removed defunct Ghost OAuth code (#848)

refs https://github.com/TryGhost/Ghost/issues/8958

- Ghost OAuth isn't coming back, time for the code to disappear and simply all the things
- fixes the `Usage of router is deprecated` notices that flood the console/test logs when testing
This commit is contained in:
Kevin Ansfield 2017-09-04 20:17:04 +01:00 committed by Katharina Irrgang
parent 7f03fa62c2
commit 6cb04e4c41
27 changed files with 208 additions and 882 deletions

View File

@ -1,40 +0,0 @@
import Oauth2Authenticator from './oauth2';
import RSVP from 'rsvp';
import {assign} from '@ember/polyfills';
import {isEmpty} from '@ember/utils';
import {run} from '@ember/runloop';
import {makeArray as wrap} from '@ember/array';
export default Oauth2Authenticator.extend({
// TODO: all this is doing is changing the `data` structure, we should
// probably create our own token auth, maybe look at
// https://github.com/jpadilla/ember-simple-auth-token
authenticate(identification, password, scope = []) {
return new RSVP.Promise((resolve, reject) => {
// const data = { 'grant_type': 'password', username: identification, password };
let data = identification;
let serverTokenEndpoint = this.get('serverTokenEndpoint');
let scopesString = wrap(scope).join(' ');
// eslint-disable-next-line camelcase
data.grant_type = 'authorization_code';
if (!isEmpty(scopesString)) {
data.scope = scopesString;
}
this.makeRequest(serverTokenEndpoint, data).then((response) => {
run(() => {
let expiresAt = this._absolutizeExpirationTime(response.expires_in);
this._scheduleAccessTokenRefresh(response.expires_in, expiresAt, response.refresh_token);
if (!isEmpty(expiresAt)) {
response = assign(response, {'expires_at': expiresAt});
}
resolve(response);
});
}, (error) => {
reject(error);
});
});
}
});

View File

@ -15,7 +15,6 @@ export default ModalComponent.extend(ValidationEngine, {
config: injectService(),
notifications: injectService(),
session: injectService(),
torii: injectService(),
identification: computed('session.user.email', function () {
return this.get('session.user.email');
@ -69,38 +68,8 @@ export default ModalComponent.extend(ValidationEngine, {
});
},
_oauthConfirm() {
// TODO: remove duplication between signin/signup/re-auth
let authStrategy = 'authenticator:oauth2-ghost';
this.toggleProperty('submitting');
this.set('authenticationError', '');
return this.get('torii')
.open('ghost-oauth2', {type: 'signin'})
.then((authentication) => {
this.get('session').set('skipAuthSuccessHandler', true);
this.get('session').authenticate(authStrategy, authentication).finally(() => {
this.get('session').set('skipAuthSuccessHandler', undefined);
this.toggleProperty('submitting');
this.get('notifications').closeAlerts();
this.send('closeModal');
});
})
.catch(() => {
this.toggleProperty('submitting');
this.set('authenticationError', 'Authentication with Ghost.org denied or failed');
});
},
reauthenticate: task(function* () {
if (this.get('config.ghostOAuth')) {
return yield this._oauthConfirm();
} else {
return yield this._passwordConfirm();
}
return yield this._passwordConfirm();
}).drop(),
actions: {

View File

@ -16,7 +16,6 @@ export default Controller.extend(ValidationEngine, {
notifications: injectService(),
session: injectService(),
settings: injectService(),
torii: injectService(),
// ValidationEngine settings
validationType: 'setup',
@ -30,33 +29,9 @@ export default Controller.extend(ValidationEngine, {
password: null,
setup: task(function* () {
if (this.get('config.ghostOAuth')) {
return yield this._oauthSetup();
} else {
return yield this._passwordSetup();
}
return yield this._passwordSetup();
}),
// TODO: remove duplication with controllers/signin
authenticateWithGhostOrg: task(function* () {
let authStrategy = 'authenticator:oauth2-ghost';
this.set('flowErrors', '');
try {
let authentication = yield this.get('torii')
.open('ghost-oauth2', {type: 'setup'});
yield this.get('authenticate').perform(authStrategy, [authentication]);
return true;
} catch (error) {
this.set('flowErrors', 'Authentication with Ghost.org denied or failed');
throw error;
}
}).drop(),
authenticate: task(function* (authStrategy, authentication) {
// we don't want to redirect after sign-in during setup
this.set('session.skipAuthSuccessHandler', true);
@ -166,41 +141,6 @@ export default Controller.extend(ValidationEngine, {
});
},
// NOTE: for OAuth ghost is in the "setup completed" step as soon
// as a user has been authenticated so we need to use the standard settings
// update to set the blog title before redirecting
_oauthSetup() {
let blogTitle = this.get('blogTitle');
let config = this.get('config');
this.get('hasValidated').addObjects(['blogTitle', 'session']);
return this.validate().then(() => {
return this.get('settings').fetch()
.then((settings) => {
settings.set('title', blogTitle);
return settings.save()
.then((settings) => {
// update the config so that the blog title shown in
// the nav bar is also updated
config.set('blogTitle', settings.get('title'));
// this.blogCreated is used by step 3 to check if step 2
// has been completed
this.set('blogCreated', true);
return this._afterAuthentication(settings);
})
.catch((error) => {
this._handleSaveError(error);
});
})
.finally(() => {
this.set('session.skipAuthSuccessHandler', undefined);
});
});
},
_handleSaveError(resp) {
if (isInvalidError(resp)) {
this.set('flowErrors', resp.errors[0].message);

View File

@ -20,7 +20,6 @@ export default Controller.extend(ValidationEngine, {
notifications: injectService(),
session: injectService(),
settings: injectService(),
torii: injectService(),
flowErrors: '',
@ -92,24 +91,6 @@ export default Controller.extend(ValidationEngine, {
}
}).drop(),
// TODO: remove duplication with controllers/setup/two
authenticateWithGhostOrg: task(function* () {
let authStrategy = 'authenticator:oauth2-ghost';
this.set('flowErrors', '');
try {
let authentication = yield this.get('torii')
.open('ghost-oauth2', {type: 'signin'});
return yield this.get('authenticate').perform(authStrategy, [authentication]);
} catch (error) {
this.set('flowErrors', 'Authentication with Ghost.org denied or failed');
throw error;
}
}).drop(),
forgotten: task(function* () {
let email = this.get('model.identification');
let forgottenUrl = this.get('ghostPaths.url').api('authentication', 'passwordreset');

View File

@ -5,7 +5,6 @@ import {
VersionMismatchError,
isVersionMismatchError
} from 'ghost-admin/services/ajax';
import {assign} from '@ember/polyfills';
import {inject as injectService} from '@ember/service';
import {isArray as isEmberArray} from '@ember/array';
import {task} from 'ember-concurrency';
@ -17,7 +16,6 @@ export default Controller.extend(ValidationEngine, {
notifications: injectService(),
session: injectService(),
settings: injectService(),
torii: injectService(),
// ValidationEngine settings
validationType: 'signup',
@ -70,27 +68,6 @@ export default Controller.extend(ValidationEngine, {
}
}).drop(),
authenticateWithGhostOrg: task(function* () {
let authStrategy = 'authenticator:oauth2-ghost';
let inviteToken = this.get('model.token');
let email = this.get('model.email');
this.set('flowErrors', '');
try {
let authentication = yield this.get('torii')
.open('ghost-oauth2', {email, type: 'invite'});
authentication = assign(authentication, {inviteToken});
return yield this.get('authenticate').perform(authStrategy, [authentication]);
} catch (error) {
this.set('flowErrors', 'Authentication with Ghost.org denied or failed');
throw error;
}
}).drop(),
signup: task(function* () {
let setupProperties = ['name', 'email', 'password', 'token'];
let notifications = this.get('notifications');

View File

@ -36,10 +36,12 @@ export default Controller.extend({
email: readOnly('model.email'),
slugValue: boundOneWay('model.slug'),
isNotOwnersProfile: not('user.isOwner'),
isAdminUserOnOwnerProfile: and('currentUser.isAdmin', 'user.isOwner'),
canAssignRoles: or('currentUser.isAdmin', 'currentUser.isOwner'),
canChangeEmail: not('isAdminUserOnOwnerProfile'),
canChangePassword: not('isAdminUserOnOwnerProfile'),
canMakeOwner: and('currentUser.isOwner', 'isNotOwnProfile', 'user.isAdmin'),
isAdminUserOnOwnerProfile: and('currentUser.isAdmin', 'user.isOwner'),
isNotOwnersProfile: not('user.isOwner'),
rolesDropdownIsVisible: and('isNotOwnProfile', 'canAssignRoles', 'isNotOwnersProfile'),
userActionsAreVisible: or('deleteUserActionIsVisible', 'canMakeOwner'),
@ -47,14 +49,6 @@ export default Controller.extend({
return this.get('user.id') === this.get('currentUser.id');
}),
isNotOwnProfile: not('isOwnProfile'),
showMyGhostLink: and('config.ghostOAuth', 'isOwnProfile'),
canChangeEmail: computed('config.ghostOAuth', 'isAdminUserOnOwnerProfile', function () {
let ghostOAuth = this.get('config.ghostOAuth');
let isAdminUserOnOwnerProfile = this.get('isAdminUserOnOwnerProfile');
return !ghostOAuth && !isAdminUserOnOwnerProfile;
}),
deleteUserActionIsVisible: computed('currentUser', 'canAssignRoles', 'user', function () {
if ((this.get('canAssignRoles') && this.get('isNotOwnProfile') && !this.get('user.isOwner'))
@ -64,10 +58,6 @@ export default Controller.extend({
}
}),
canChangePassword: computed('config.ghostOAuth', 'isAdminUserOnOwnerProfile', function () {
return !this.get('config.ghostOAuth') && !this.get('isAdminUserOnOwnerProfile');
}),
// duplicated in gh-user-active -- find a better home and consolidate?
userDefault: computed('ghostPaths', function () {
return `${this.get('ghostPaths.assetRoot')}/img/user-image.png`;

View File

@ -17,11 +17,8 @@ export default Route.extend(styleBody, {
beforeModel() {
this._super(...arguments);
// with OAuth auth users are authenticated on step 2 so we
// can't use the session.isAuthenticated shortcut
if (!this.get('config.ghostOAuth') && this.get('session.isAuthenticated')) {
this.transitionTo('posts');
return;
if (this.get('session.isAuthenticated')) {
return this.transitionTo('posts');
}
let authUrl = this.get('ghostPaths.url').api('authentication', 'setup');

View File

@ -3,7 +3,6 @@ import Service from '@ember/service';
import {assign} from '@ember/polyfills';
import {computed} from '@ember/object';
import {inject as injectService} from '@ember/service';
import {isBlank} from '@ember/utils';
// ember-cli-shims doesn't export _ProxyMixin
const {_ProxyMixin} = Ember;
@ -46,10 +45,6 @@ export default Service.extend(_ProxyMixin, {
});
}),
ghostOAuth: computed('ghostAuthId', function () {
return !isBlank(this.get('ghostAuthId'));
}),
blogDomain: computed('blogUrl', function () {
let blogUrl = this.get('blogUrl');
let blogDomain = blogUrl

View File

@ -5,18 +5,14 @@
<div class="modal-body {{if authenticationError 'error'}}">
{{#if config.ghostOAuth}}
{{gh-task-button "Sign in with Ghost" task=reauthenticate class="login gh-btn gh-btn-blue gh-btn-block gh-btn-icon" tabindex="3" autoWidth="false"}}
{{else}}
<form id="login" class="login-form" method="post" novalidate="novalidate" {{action "confirm" on="submit"}}>
{{#gh-validation-status-container class="password-wrap" errors=errors property="password" hasValidated=hasValidated}}
{{gh-input password class="password" type="password" placeholder="Password" name="password" update=(action (mut password))}}
{{/gh-validation-status-container}}
<div>
{{gh-task-button "Log in" task=reauthenticate class="gh-btn gh-btn-blue gh-btn-icon" type="submit"}}
</div>
</form>
{{/if}}
<form id="login" class="login-form" method="post" novalidate="novalidate" {{action "confirm" on="submit"}}>
{{#gh-validation-status-container class="password-wrap" errors=errors property="password" hasValidated=hasValidated}}
{{gh-input password class="password" type="password" placeholder="Password" name="password" update=(action (mut password))}}
{{/gh-validation-status-container}}
<div>
{{gh-task-button "Log in" task=reauthenticate class="gh-btn gh-btn-blue gh-btn-icon" type="submit"}}
</div>
</form>
{{#if authenticationError}}
<p class="response">{{authenticationError}}</p>

View File

@ -1,55 +1,87 @@
{{#if config.ghostOAuth}}
<header>
<h1>Setup your blog</h1>
</header>
<header>
<h1>Create your account</h1>
</header>
<form id="setup" class="gh-flow-create" {{action "setup" on="submit"}}>
{{#gh-form-group errors=errors hasValidated=hasValidated property="session"}}
{{#gh-task-button
task=authenticateWithGhostOrg
class="login gh-btn gh-btn-blue gh-btn-block gh-btn-icon"
type="button"
successClass=""
as |task|
}}
{{#if task.isRunning}}
<span>{{inline-svg "spinner" class="gh-icon-spinner gh-btn-icon-no-margin"}}</span>
{{else}}
{{#if session.isAuthenticated}}
<span>Connected: {{session.user.email}}</span>
{{else}}
<span>Sign in with Ghost</span>
{{/if}}
{{/if}}
{{/gh-task-button}}
{{gh-error-message errors=errors property="session"}}
{{/gh-form-group}}
<form id="setup" class="gh-flow-create">
{{!-- Horrible hack to prevent Chrome from incorrectly auto-filling inputs --}}
<input style="display:none;" type="text" name="username"/>
<input style="display:none;" type="password" name="password"/>
{{#gh-form-group errors=errors hasValidated=hasValidated property="blogTitle"}}
<label for="blog-title">Blog title</label>
<span class="gh-input-icon gh-icon-content">
{{inline-svg "content"}}
{{gh-input blogTitle
tabindex="4"
type="text"
name="blog-title"
placeholder="Eg. The Daily Awesome"
autocorrect="off"
focusOut=(action "preValidate" "blogTitle")
update=(action (mut blogTitle))
onenter=(action "setup")}}
</span>
{{gh-error-message errors=errors property="blogTitle"}}
{{/gh-form-group}}
</form>
{{gh-profile-image email=email setImage=(action "setImage")}}
{{#gh-form-group errors=errors hasValidated=hasValidated property="blogTitle"}}
<label for="blog-title">Blog title</label>
<span class="gh-input-icon gh-icon-content">
{{inline-svg "content"}}
{{gh-trim-focus-input blogTitle
tabindex="1"
type="text"
name="blog-title"
placeholder="Eg. The Daily Awesome"
autocorrect="off"
focusOut=(action "preValidate" "blogTitle")
update=(action (mut blogTitle))
data-test-blog-title-input=true}}
</span>
{{gh-error-message errors=errors property="blogTitle"}}
{{/gh-form-group}}
{{#gh-form-group errors=errors hasValidated=hasValidated property="name"}}
<label for="name">Full name</label>
<span class="gh-input-icon gh-icon-user">
{{inline-svg "user-circle"}}
{{gh-input name
tabindex="2"
type="text"
name="name"
placeholder="Eg. John H. Watson"
autocorrect="off"
focusOut=(action "preValidate" "name")
update=(action (mut name))
data-test-name-input=true}}
</span>
{{gh-error-message errors=errors property="name"}}
{{/gh-form-group}}
{{#gh-form-group errors=errors hasValidated=hasValidated property="email"}}
<label for="email">Email address</label>
<span class="gh-input-icon gh-icon-mail">
{{inline-svg "email"}}
{{gh-input email
tabindex="3"
type="email"
name="email"
placeholder="Eg. john@example.com"
autocorrect="off"
focusOut=(action "preValidate" "email")
update=(action (mut email))
data-test-email-input=true}}
</span>
{{gh-error-message errors=errors property="email"}}
{{/gh-form-group}}
{{#gh-form-group errors=errors hasValidated=hasValidated property="password"}}
<label for="password">Password</label>
<span class="gh-input-icon gh-icon-lock">
{{inline-svg "lock"}}
{{gh-input password
tabindex="4"
type="password"
name="password"
placeholder="At least 8 characters"
autocorrect="off"
focusOut=(action "preValidate" "password")
update=(action (mut password))
data-test-password-input=true}}
</span>
{{gh-error-message errors=errors property="password"}}
{{/gh-form-group}}
{{#gh-task-button
task=setup
type="submit"
tabindex="5"
class="gh-btn gh-btn-green gh-btn-lg gh-btn-block gh-btn-icon"
disabled=submitDisabled
data-test-submit-button=true
as |task|
}}
{{#if task.isRunning}}
@ -58,101 +90,6 @@
<span>Last step: Invite your team {{inline-svg "arrow-right-small" class="gh-btn-icon-right"}}</span>
{{/if}}
{{/gh-task-button}}
{{else}}
<header>
<h1>Create your account</h1>
</header>
<form id="setup" class="gh-flow-create">
{{!-- Horrible hack to prevent Chrome from incorrectly auto-filling inputs --}}
<input style="display:none;" type="text" name="username"/>
<input style="display:none;" type="password" name="password"/>
{{gh-profile-image email=email setImage=(action "setImage")}}
{{#gh-form-group errors=errors hasValidated=hasValidated property="blogTitle"}}
<label for="blog-title">Blog title</label>
<span class="gh-input-icon gh-icon-content">
{{inline-svg "content"}}
{{gh-trim-focus-input blogTitle
tabindex="1"
type="text"
name="blog-title"
placeholder="Eg. The Daily Awesome"
autocorrect="off"
focusOut=(action "preValidate" "blogTitle")
update=(action (mut blogTitle))
data-test-blog-title-input=true}}
</span>
{{gh-error-message errors=errors property="blogTitle"}}
{{/gh-form-group}}
{{#gh-form-group errors=errors hasValidated=hasValidated property="name"}}
<label for="name">Full name</label>
<span class="gh-input-icon gh-icon-user">
{{inline-svg "user-circle"}}
{{gh-input name
tabindex="2"
type="text"
name="name"
placeholder="Eg. John H. Watson"
autocorrect="off"
focusOut=(action "preValidate" "name")
update=(action (mut name))
data-test-name-input=true}}
</span>
{{gh-error-message errors=errors property="name"}}
{{/gh-form-group}}
{{#gh-form-group errors=errors hasValidated=hasValidated property="email"}}
<label for="email">Email address</label>
<span class="gh-input-icon gh-icon-mail">
{{inline-svg "email"}}
{{gh-input email
tabindex="3"
type="email"
name="email"
placeholder="Eg. john@example.com"
autocorrect="off"
focusOut=(action "preValidate" "email")
update=(action (mut email))
data-test-email-input=true}}
</span>
{{gh-error-message errors=errors property="email"}}
{{/gh-form-group}}
{{#gh-form-group errors=errors hasValidated=hasValidated property="password"}}
<label for="password">Password</label>
<span class="gh-input-icon gh-icon-lock">
{{inline-svg "lock"}}
{{gh-input password
tabindex="4"
type="password"
name="password"
placeholder="At least 8 characters"
autocorrect="off"
focusOut=(action "preValidate" "password")
update=(action (mut password))
data-test-password-input=true}}
</span>
{{gh-error-message errors=errors property="password"}}
{{/gh-form-group}}
{{#gh-task-button
task=setup
type="submit"
tabindex="5"
class="gh-btn gh-btn-green gh-btn-lg gh-btn-block gh-btn-icon"
as |task|
}}
{{#if task.isRunning}}
<span>{{inline-svg "spinner" class="gh-icon-spinner gh-btn-icon-no-margin"}}</span>
{{else}}
<span>Last step: Invite your team {{inline-svg "arrow-right-small" class="gh-btn-icon-right"}}</span>
{{/if}}
{{/gh-task-button}}
</form>
{{/if}}
</form>
<p class="main-error">{{{flowErrors}}}</p>

View File

@ -1,66 +1,53 @@
<div class="gh-flow">
<div class="gh-flow-content-wrap">
<section class="gh-flow-content">
{{#if config.ghostOAuth}}
<header>
<h1>{{config.blogTitle}}</h1>
</header>
{{/if}}
<form id="login" method="post" class="gh-signin" novalidate="novalidate" {{action "authenticate" on="submit"}}>
{{#if config.ghostOAuth}}
{{gh-task-button "Sign in with Ghost"
task=authenticateWithGhostOrg
class="login gh-btn gh-btn-blue gh-btn-block gh-btn-icon"
tabindex="3"}}
{{else}}
{{#gh-form-group errors=model.errors hasValidated=hasValidated property="identification"}}
<span class="gh-input-icon gh-icon-mail">
{{inline-svg "email"}}
{{gh-trim-focus-input model.identification
class="email"
type="email"
placeholder="Email Address"
name="identification"
autocapitalize="off"
autocorrect="off"
tabindex="1"
focusOut=(action "validate" "identification")
update=(action (mut model.identification))}}
</span>
{{/gh-form-group}}
{{#gh-form-group errors=model.errors hasValidated=hasValidated property="password"}}
<span class="gh-input-icon gh-icon-lock forgotten-wrap">
{{inline-svg "lock"}}
{{gh-input model.password
class="password"
type="password"
placeholder="Password"
name="password"
tabindex="2"
autocorrect="off"
update=(action (mut model.password))}}
{{#gh-form-group errors=model.errors hasValidated=hasValidated property="identification"}}
<span class="gh-input-icon gh-icon-mail">
{{inline-svg "email"}}
{{gh-trim-focus-input model.identification
class="email"
type="email"
placeholder="Email Address"
name="identification"
autocapitalize="off"
autocorrect="off"
tabindex="1"
focusOut=(action "validate" "identification")
update=(action (mut model.identification))}}
</span>
{{/gh-form-group}}
{{#gh-form-group errors=model.errors hasValidated=hasValidated property="password"}}
<span class="gh-input-icon gh-icon-lock forgotten-wrap">
{{inline-svg "lock"}}
{{gh-input model.password
class="password"
type="password"
placeholder="Password"
name="password"
tabindex="2"
autocorrect="off"
update=(action (mut model.password))}}
{{#gh-task-button
task=forgotten
class="forgotten-link gh-btn gh-btn-link gh-btn-icon"
tabindex="4"
type="button"
successClass=""
failureClass=""
as |task|
}}
<span>{{#if task.isRunning}}{{inline-svg "spinner" class="gh-spinner"}}{{else}}Forgot?{{/if}}</span>
{{/gh-task-button}}
</span>
{{/gh-form-group}}
{{#gh-task-button
task=forgotten
class="forgotten-link gh-btn gh-btn-link gh-btn-icon"
tabindex="4"
type="button"
successClass=""
failureClass=""
as |task|
}}
<span>{{#if task.isRunning}}{{inline-svg "spinner" class="gh-spinner"}}{{else}}Forgot?{{/if}}</span>
{{/gh-task-button}}
</span>
{{/gh-form-group}}
{{gh-task-button "Sign in"
task=validateAndAuthenticate
class="login gh-btn gh-btn-blue gh-btn-block gh-btn-icon"
type="submit"
tabindex="3"}}
{{/if}}
{{gh-task-button "Sign in"
task=validateAndAuthenticate
class="login gh-btn gh-btn-blue gh-btn-block gh-btn-icon"
type="submit"
tabindex="3"}}
</form>
<p class="main-error">{{{if flowErrors flowErrors "&nbsp;"}}}</p>

View File

@ -2,89 +2,70 @@
<div class="gh-flow-content-wrap">
<section class="gh-flow-content">
{{#if config.ghostOAuth}}
<header>
<h1>{{config.blogTitle}}</h1>
<p>
{{!-- TODO: show invite creator's name/email --}}
Accept your invite from <strong>{{model.invitedBy}}</strong>
</p>
</header>
<header>
<h1>Create your account</h1>
</header>
<form id="signup" class="gh-signin" method="post" novalidate="novalidate">
{{gh-task-button "Sign in with Ghost to accept"
task=authenticateWithGhostOrg
type="submit"
class="login gh-btn gh-btn-blue gh-btn-block gh-btn-icon"
tabindex="3"}}
</form>
<form id="signup" class="gh-flow-create" method="post" novalidate="novalidate">
{{!-- Hack to stop Chrome's broken auto-fills --}}
<input style="display:none;" type="text" name="fakeusernameremembered"/>
<input style="display:none;" type="password" name="fakepasswordremembered"/>
{{else}}
<header>
<h1>Create your account</h1>
</header>
{{gh-profile-image email=model.email setImage=(action "setImage")}}
<form id="signup" class="gh-flow-create" method="post" novalidate="novalidate">
{{!-- Hack to stop Chrome's broken auto-fills --}}
<input style="display:none;" type="text" name="fakeusernameremembered"/>
<input style="display:none;" type="password" name="fakepasswordremembered"/>
{{#gh-form-group}}
<label for="email-address">Email address</label>
<span class="gh-input-icon gh-icon-mail">
{{inline-svg "email"}}
{{gh-input model.email
type="email"
name="email"
placeholder="Eg. john@example.com"
disabled="disabled"
autocorrect="off"}}
</span>
{{/gh-form-group}}
{{gh-profile-image email=model.email setImage=(action "setImage")}}
{{#gh-form-group errors=model.errors hasValidated=hasValidated property="name"}}
<label for="full-name">Full name</label>
<span class="gh-input-icon gh-icon-user">
{{inline-svg "user-circle"}}
{{gh-trim-focus-input model.name
tabindex="1"
type="text"
name="name"
placeholder="Eg. John H. Watson"
autocorrect="off"
onenter=(action "signup")
focusOut=(action "validate" "name")
update=(action (mut model.name))}}
</span>
{{gh-error-message errors=model.errors property="name"}}
{{/gh-form-group}}
{{#gh-form-group}}
<label for="email-address">Email address</label>
<span class="gh-input-icon gh-icon-mail">
{{inline-svg "email"}}
{{gh-input model.email
type="email"
name="email"
placeholder="Eg. john@example.com"
disabled="disabled"
autocorrect="off"}}
</span>
{{/gh-form-group}}
{{#gh-form-group errors=model.errors hasValidated=hasValidated property="password"}}
<label for="password">Password</label>
<span class="gh-input-icon gh-icon-lock">
{{inline-svg "lock"}}
{{gh-input model.password
tabindex="2"
type="password"
name="password"
placeholder="At least 8 characters"
onenter=(action "signup")
autocorrect="off"
focusOut=(action "validate" "password")
update=(action (mut model.password))}}
</span>
{{gh-error-message errors=model.errors property="password"}}
{{/gh-form-group}}
</form>
{{#gh-form-group errors=model.errors hasValidated=hasValidated property="name"}}
<label for="full-name">Full name</label>
<span class="gh-input-icon gh-icon-user">
{{inline-svg "user-circle"}}
{{gh-trim-focus-input model.name
tabindex="1"
type="text"
name="name"
placeholder="Eg. John H. Watson"
autocorrect="off"
onenter=(action "signup")
focusOut=(action "validate" "name")
update=(action (mut model.name))}}
</span>
{{gh-error-message errors=model.errors property="name"}}
{{/gh-form-group}}
{{#gh-form-group errors=model.errors hasValidated=hasValidated property="password"}}
<label for="password">Password</label>
<span class="gh-input-icon gh-icon-lock">
{{inline-svg "lock"}}
{{gh-input model.password
tabindex="2"
type="password"
name="password"
placeholder="At least 8 characters"
onenter=(action "signup")
autocorrect="off"
focusOut=(action "validate" "password")
update=(action (mut model.password))}}
</span>
{{gh-error-message errors=model.errors property="password"}}
{{/gh-form-group}}
</form>
{{gh-task-button "Create Account"
runningText="Creating"
task=signup
class="gh-btn gh-btn-green gh-btn-lg gh-btn-block gh-btn-icon"
tabindex="3"}}
{{/if}}
{{gh-task-button "Create Account"
runningText="Creating"
task=signup
class="gh-btn gh-btn-green gh-btn-lg gh-btn-block gh-btn-icon"
tabindex="3"}}
<p class="main-error">{{{if flowErrors flowErrors "&nbsp;"}}}</p>
</section>

View File

@ -236,12 +236,5 @@
</fieldset>
</form> {{! change password form }}
{{/if}}
{{!-- when using Ghost OAuth, users trying to change email/pass need to visit my.ghost.org --}}
{{#if showMyGhostLink}}
<div class="user-profile">
<p class="gh-box gh-box-info">{{inline-svg "lock"}} To change your login details please visit <a href="https://my.ghost.org/account" target="_blank">https://my.ghost.org/account</a></p>
</div>
{{/if}}
</div>
</section>

View File

@ -1,44 +0,0 @@
import Oauth2 from 'torii/providers/oauth2-code';
import ghostPaths from 'ghost-admin/utils/ghost-paths';
import {computed} from '@ember/object';
import {inject as injectService} from '@ember/service';
let GhostOauth2 = Oauth2.extend({
config: injectService(),
name: 'ghost-oauth2',
baseUrl: computed(function () {
return `${this.get('config.ghostAuthUrl')}/oauth2/authorize/`;
}),
apiKey: computed(function () {
return this.get('config.ghostAuthId');
}),
optionalUrlParams: ['type', 'email'],
responseParams: ['code'],
// we want to redirect to the ghost admin app by default
init() {
this._super(...arguments);
let adminPath = ghostPaths().adminRoot;
let redirectUri = `${window.location.protocol}//${window.location.host}`;
redirectUri += adminPath;
this.set('redirectUri', redirectUri);
},
open(options) {
if (options.type) {
this.set('type', options.type);
}
if (options.email) {
this.set('email', options.email);
}
return this._super(...arguments);
}
});
export default GhostOauth2;

View File

@ -4,23 +4,17 @@ export default BaseValidator.extend({
properties: ['name', 'email', 'password'],
name(model) {
let usingOAuth = model.get('config.ghostOAuth');
let name = model.get('name');
if (!usingOAuth && !validator.isLength(name, 1)) {
if (!validator.isLength(name, 1)) {
model.get('errors').add('name', 'Please enter a name.');
this.invalidate();
}
},
email(model) {
let usingOAuth = model.get('config.ghostOAuth');
let email = model.get('email');
if (usingOAuth) {
return;
}
if (validator.empty(email)) {
model.get('errors').add('email', 'Please enter an email.');
this.invalidate();
@ -31,10 +25,9 @@ export default BaseValidator.extend({
},
password(model) {
let usingOAuth = model.get('config.ghostOAuth');
let password = model.get('password');
if (!usingOAuth && !validator.isLength(password, 8)) {
if (!validator.isLength(password, 8)) {
model.get('errors').add('password', 'Password must be at least 8 characters long');
this.invalidate();
}

View File

@ -1,7 +1,7 @@
import NewUserValidator from 'ghost-admin/validators/new-user';
export default NewUserValidator.create({
properties: ['name', 'email', 'password', 'blogTitle', 'session'],
properties: ['name', 'email', 'password', 'blogTitle'],
blogTitle(model) {
let blogTitle = model.get('blogTitle');
@ -10,16 +10,5 @@ export default NewUserValidator.create({
model.get('errors').add('blogTitle', 'Please enter a blog title.');
this.invalidate();
}
},
session(model) {
let usingOAuth = model.get('config.ghostOAuth');
let isAuthenticated = model.get('session.isAuthenticated');
if (usingOAuth && !isAuthenticated) {
model.get('errors').add('session', 'Please connect a Ghost.org account before continuing');
model.get('hasValidated').pushObject('session');
this.invalidate();
}
}
});

View File

@ -35,9 +35,7 @@ module.exports = function (environment) {
moment: {
includeTimezone: 'all'
},
torii: { }
}
};
if (environment === 'development') {

View File

@ -1,33 +1,16 @@
/* eslint-disable camelcase */
import $ from 'jquery';
import {Response} from 'ember-cli-mirage';
import {isBlank} from '@ember/utils';
export default function mockAuthentication(server) {
server.post('/authentication/token', function ({roles, users}, {requestBody}) {
let params = $.deparam(requestBody);
if (params.grant_type === 'authorization_code') {
// OAuth sign-in
if (!users.all().models.length) {
let role = roles.findBy({name: 'Owner'});
server.create('user', {email: 'oauthtest@example.com', roles: [role]});
}
return {
access_token: 'MirageAccessToken',
expires_in: 172800,
refresh_token: 'MirageRefreshToken'
};
} else {
// Password sign-in
return {
access_token: 'MirageAccessToken',
expires_in: 172800,
refresh_token: 'MirageRefreshToken',
token_type: 'Bearer'
};
}
server.post('/authentication/token', function () {
// Password sign-in
return {
access_token: 'MirageAccessToken',
expires_in: 172800,
refresh_token: 'MirageRefreshToken',
token_type: 'Bearer'
};
});
server.post('/authentication/passwordreset', function (schema, request) {

View File

@ -4,9 +4,6 @@ export default [{
clientId: 'ghost-admin',
clientSecret: '1234ClientSecret',
fileStorage: 'true',
// these are valid attrs but we want password auth by default in tests
// ghostAuthId: '1234GhostAuthId',
// ghostAuthUrl: 'http://devauth.ghost.org:8080',
internalTags: 'false',
publicAPI: 'false',
routeKeywords: {

View File

@ -119,7 +119,6 @@
"simplemde": "https://github.com/kevinansfield/simplemde-markdown-editor.git#ghost",
"testem": "1.18.4",
"top-gh-contribs": "2.0.4",
"torii": "0.8.2",
"walk-sync": "0.3.2"
},
"ember-addon": {

View File

@ -5,12 +5,7 @@ import startApp from '../helpers/start-app';
import {Response} from 'ember-cli-mirage';
import {afterEach, beforeEach, describe, it} from 'mocha';
import {authenticateSession, invalidateSession} from '../helpers/ember-simple-auth';
import {enableGhostOAuth} from '../helpers/configuration';
import {expect} from 'chai';
import {
stubFailedOAuthConnect,
stubSuccessfulOAuthConnect
} from '../helpers/oauth';
describe('Acceptance: Setup', function () {
let application;
@ -363,103 +358,4 @@ describe('Acceptance: Setup', function () {
.to.equal(1);
});
});
describe('using Ghost OAuth', function () {
beforeEach(function () {
// mimic a new install
server.get('/authentication/setup/', function () {
return {
setup: [
{status: false}
]
};
});
// ensure we have settings (to pass validation) and roles available
enableGhostOAuth(server);
server.loadFixtures('settings');
server.loadFixtures('roles');
});
it('displays the connect form and validates', async function () {
invalidateSession(application);
await visit('/setup');
// it redirects to step one
expect(
currentURL(),
'url after accessing /setup'
).to.equal('/setup/one');
await click('.gh-btn-green');
expect(
find('button.login').text().trim(),
'login button text'
).to.equal('Sign in with Ghost');
await click('.gh-btn-green');
let sessionFG = find('button.login').closest('.form-group');
let titleFG = find('input[name="blog-title"]').closest('.form-group');
// session is validated
expect(
sessionFG.hasClass('error'),
'session form group has error class'
).to.be.true;
expect(
sessionFG.find('.response').text().trim(),
'session validation text'
).to.match(/Please connect a Ghost\.org account/i);
// blog title is validated
expect(
titleFG.hasClass('error'),
'title form group has error class'
).to.be.true;
expect(
titleFG.find('.response').text().trim(),
'title validation text'
).to.match(/please enter a blog title/i);
// TODO: test that connecting clears session validation error
// TODO: test that typing in blog title clears validation error
});
it('can connect and setup successfully', async function () {
stubSuccessfulOAuthConnect(application);
await visit('/setup/two');
await click('button.login');
expect(
find('button.login').text().trim(),
'login button text when connected'
).to.equal('Connected: oauthtest@example.com');
await fillIn('input[name="blog-title"]', 'Ghostbusters');
await click('[data-test-submit-button]');
expect(
currentURL(),
'url after submitting'
).to.equal('/setup/three');
});
it('handles failed connect', async function () {
stubFailedOAuthConnect(application);
await visit('/setup/two');
await click('button.login');
expect(
find('.main-error').text().trim(),
'error text after failed oauth connect'
).to.match(/authentication with ghost\.org denied or failed/i);
});
});
});

View File

@ -10,12 +10,7 @@ import {
it
} from 'mocha';
import {authenticateSession, invalidateSession} from '../helpers/ember-simple-auth';
import {enableGhostOAuth} from '../helpers/configuration';
import {expect} from 'chai';
import {
stubFailedOAuthConnect,
stubSuccessfulOAuthConnect
} from '../helpers/oauth';
describe('Acceptance: Signin', function() {
let application;
@ -119,42 +114,4 @@ describe('Acceptance: Signin', function() {
expect(currentURL(), 'currentURL').to.equal('/');
});
});
describe('using Ghost OAuth', function () {
beforeEach(function () {
enableGhostOAuth(server);
});
it('can sign in successfully', async function () {
server.loadFixtures('roles');
stubSuccessfulOAuthConnect(application);
await visit('/signin');
expect(currentURL(), 'current url').to.equal('/signin');
expect(
find('button.login').text().trim(),
'login button text'
).to.equal('Sign in with Ghost');
await click('button.login');
expect(currentURL(), 'url after connect').to.equal('/');
});
it('handles a failed connect', async function () {
stubFailedOAuthConnect(application);
await visit('/signin');
await click('button.login');
expect(currentURL(), 'current url').to.equal('/signin');
expect(
find('.main-error').text().trim(),
'sign-in error'
).to.match(/Authentication with Ghost\.org denied or failed/i);
});
});
});

View File

@ -7,12 +7,7 @@ import {
describe,
it
} from 'mocha';
import {enableGhostOAuth} from '../helpers/configuration';
import {expect} from 'chai';
import {
stubFailedOAuthConnect,
stubSuccessfulOAuthConnect
} from '../helpers/oauth';
describe('Acceptance: Signup', function() {
let application;
@ -130,55 +125,4 @@ describe('Acceptance: Signup', function() {
it('redirects if already logged in');
it('redirects with alert on invalid token');
it('redirects with alert on non-existant or expired token');
describe('using Ghost OAuth', function () {
beforeEach(function () {
enableGhostOAuth(server);
let {invites, users} = server.schema;
let user = users.create({name: 'Test Invite Creator'});
invites.create({
email: 'kevin+test2@ghost.org',
createdBy: user.id
});
});
it('can sign up sucessfully', async function () {
stubSuccessfulOAuthConnect(application);
// token details:
// "1470346017929|kevin+test2@ghost.org|2cDnQc3g7fQTj9nNK4iGPSGfvomkLdXf68FuWgS66Ug="
await visit('/signup/MTQ3MDM0NjAxNzkyOXxrZXZpbit0ZXN0MkBnaG9zdC5vcmd8MmNEblFjM2c3ZlFUajluTks0aUdQU0dmdm9ta0xkWGY2OEZ1V2dTNjZVZz0');
expect(currentPath()).to.equal('signup');
expect(
find('.gh-flow-content header p').text().trim(),
'form header text'
).to.equal('Accept your invite from Test Invite Creator');
await click('button.login');
expect(currentPath()).to.equal('posts.index');
});
it('handles failed connect', async function () {
stubFailedOAuthConnect(application);
// token details:
// "1470346017929|kevin+test2@ghost.org|2cDnQc3g7fQTj9nNK4iGPSGfvomkLdXf68FuWgS66Ug="
await visit('/signup/MTQ3MDM0NjAxNzkyOXxrZXZpbit0ZXN0MkBnaG9zdC5vcmd8MmNEblFjM2c3ZlFUajluTks0aUdQU0dmdm9ta0xkWGY2OEZ1V2dTNjZVZz0');
await click('button.login');
expect(currentPath()).to.equal('signup');
expect(
find('.main-error').text().trim(),
'flow error text'
).to.match(/authentication with ghost\.org denied or failed/i);
});
});
});

View File

@ -6,7 +6,6 @@ import startApp from '../helpers/start-app';
import {Response} from 'ember-cli-mirage';
import {afterEach, beforeEach, describe, it} from 'mocha';
import {authenticateSession, invalidateSession} from '../helpers/ember-simple-auth';
import {enableGhostOAuth} from '../helpers/configuration';
import {errorOverride, errorReset} from '../helpers/adapter-error';
import {expect} from 'chai';
@ -715,34 +714,6 @@ describe('Acceptance: Team', function () {
});
});
describe('using Ghost OAuth', function () {
beforeEach(function () {
enableGhostOAuth(server);
});
it('doesn\'t show the password reset form', async function () {
await visit(`/team/${admin.slug}`);
// ensure that the normal form is displayed so we don't get
// false positives
expect(
find('input#user-slug').length,
'profile form is displayed'
).to.equal(1);
// check that the password form is hidden
expect(
find('#password-reset').length,
'presence of password reset form'
).to.equal(0);
expect(
find('#user-password-new').length,
'presence of new password field'
).to.equal(0);
});
});
describe('own user', function () {
it('requires current password when changing password', async function () {
await visit(`/team/${admin.slug}`);

View File

@ -1,12 +0,0 @@
import {isEmpty} from '@ember/utils';
export function enableGhostOAuth(server) {
if (isEmpty(server.db.configurations)) {
server.loadFixtures('configurations');
}
server.db.configurations.update(1, {
ghostAuthId: '6e0704b3-c653-4c12-8da7-584232b5c629',
ghostAuthUrl: 'http://devauth.ghost.org:8080'
});
}

View File

@ -1,39 +0,0 @@
import RSVP from 'rsvp';
import {faker} from 'ember-cli-mirage';
let generateCode = function generateCode() {
return faker.internet.password(32, false, /[a-zA-Z0-9]/);
};
let generateSecret = function generateSecret() {
return faker.internet.password(12, false, /[a-f0-9]/);
};
const stubSuccessfulOAuthConnect = function stubSuccessfulOAuthConnect(application) {
let provider = application.__container__.lookup('torii-provider:ghost-oauth2');
provider.open = function () {
return RSVP.Promise.resolve({
/* eslint-disable camelcase */
authorizationCode: generateCode(),
client_id: 'ghost-admin',
client_secret: generateSecret(),
provider: 'ghost-oauth2',
redirectUrl: 'http://localhost:2368/ghost/'
/* eslint-enable camelcase */
});
};
};
const stubFailedOAuthConnect = function stubFailedOAuthConnect(application) {
let provider = application.__container__.lookup('torii-provider:ghost-oauth2');
provider.open = function () {
return RSVP.Promise.reject();
};
};
export {
stubSuccessfulOAuthConnect,
stubFailedOAuthConnect
};

View File

@ -3017,7 +3017,7 @@ ember-cli-babel@6.8.2, ember-cli-babel@^6.0.0, ember-cli-babel@^6.0.0-beta.10, e
clone "^2.0.0"
ember-cli-version-checker "^2.0.0"
ember-cli-babel@^5.0.0, ember-cli-babel@^5.1.3, ember-cli-babel@^5.1.5, ember-cli-babel@^5.1.6, ember-cli-babel@^5.1.7:
ember-cli-babel@^5.0.0, ember-cli-babel@^5.1.5, ember-cli-babel@^5.1.6, ember-cli-babel@^5.1.7:
version "5.2.4"
resolved "https://registry.yarnpkg.com/ember-cli-babel/-/ember-cli-babel-5.2.4.tgz#5ce4f46b08ed6f6d21e878619fb689719d6e8e13"
dependencies:
@ -9038,15 +9038,6 @@ top-gh-contribs@2.0.4:
lodash "^4.11.1"
request "^2.72.0"
torii@0.8.2:
version "0.8.2"
resolved "https://registry.yarnpkg.com/torii/-/torii-0.8.2.tgz#60688b5ce0bc148dedf764df3c4c41f55da3f866"
dependencies:
broccoli-funnel "^1.0.1"
broccoli-merge-trees "^1.1.1"
broccoli-string-replace "^0.1.1"
ember-cli-babel "^5.1.3"
tough-cookie@~2.3.0:
version "2.3.2"
resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.3.2.tgz#f081f76e4c85720e6c37a5faced737150d84072a"