mirror of
https://github.com/vale981/accounts-ui
synced 2025-03-05 18:01:39 -05:00
* Buttons for oauth services
* Option for "NO_PASSWORD" changed to "EMAIL_ONLY_NO_PASSWORD" * Added new options to accounts-password "USERNAME_AND_EMAIL_NO_PASSWORD".
This commit is contained in:
parent
b0c43edcb6
commit
19864ef983
12 changed files with 191 additions and 49 deletions
|
@ -1,5 +1,11 @@
|
|||
# ChangeLog
|
||||
|
||||
### v1.0.21
|
||||
|
||||
* Buttons for oauth services
|
||||
* Option for "NO_PASSWORD" changed to "EMAIL_ONLY_NO_PASSWORD"
|
||||
* Added new options to accounts-password "USERNAME_AND_EMAIL_NO_PASSWORD".
|
||||
|
||||
### v1.0.20
|
||||
|
||||
* Clear the password when logging in or out.
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# React Accounts UI
|
||||
|
||||
Current version 1.0.20
|
||||
Current version 1.0.21
|
||||
|
||||
## Features
|
||||
|
||||
|
@ -48,7 +48,7 @@ Configure the behavior of `<Accounts.ui.LoginForm />`
|
|||
If true, forces the user to approve the app's permissions, even if previously approved. Currently only supported with Google.
|
||||
|
||||
* **passwordSignupFields** String
|
||||
Which fields to display in the user creation form. One of `'USERNAME_AND_EMAIL'`, `'USERNAME_AND_OPTIONAL_EMAIL'`, `'USERNAME_ONLY'`, or `'EMAIL_ONLY'`, **`'NO_PASSWORD'`** (**default**).
|
||||
Which fields to display in the user creation form. One of `'USERNAME_AND_EMAIL'`, `'USERNAME_AND_OPTIONAL_EMAIL'`, `'USERNAME_ONLY'`, `'EMAIL_ONLY'`, `'USERNAME_AND_EMAIL_NO_PASSWORD'`, **`'EMAIL_ONLY_NO_PASSWORD'`** (**default**).
|
||||
|
||||
* **loginPath** String
|
||||
Change the default path a user should be redirected to after a clicking a link in a mail provided to them from the accounts system, it could be a mail set to them when they have reset their password, verifying their email if the setting for `sendVerificationEmail` is turned on ([read more on accounts configuration ](http://docs.meteor.com/#/full/accounts_config)).
|
||||
|
|
|
@ -68,7 +68,8 @@ Accounts.ui.config = function(options) {
|
|||
"USERNAME_AND_OPTIONAL_EMAIL",
|
||||
"USERNAME_ONLY",
|
||||
"EMAIL_ONLY",
|
||||
"NO_PASSWORD"
|
||||
"EMAIL_ONLY_NO_PASSWORD",
|
||||
"USERNAME_AND_EMAIL_NO_PASSWORD"
|
||||
], options.passwordSignupFields)) {
|
||||
Accounts.ui._options.passwordSignupFields = options.passwordSignupFields;
|
||||
}
|
||||
|
|
|
@ -7,14 +7,30 @@ import { Accounts } from 'meteor/accounts-base';
|
|||
|
||||
// Method called by a user to request a password reset email. This is
|
||||
// the start of the reset process.
|
||||
Meteor.methods({loginWithoutPassword: function (options) {
|
||||
check(options, {email: String});
|
||||
Meteor.methods({loginWithoutPassword: function ({ email, username = null }) {
|
||||
if (username !== null) {
|
||||
check(username, String);
|
||||
|
||||
var user = Meteor.users.findOne({"emails.address": options.email});
|
||||
if (!user)
|
||||
throw new Meteor.Error(403, "User not found");
|
||||
var user = Meteor.users.findOne({ $or: [{
|
||||
"username": username, "emails.address": { $exists: 1 }
|
||||
}, {
|
||||
"emails.address": email
|
||||
}]
|
||||
});
|
||||
if (!user)
|
||||
throw new Meteor.Error(403, "User not found");
|
||||
|
||||
Accounts.sendLoginEmail(user._id, options.email);
|
||||
email = user.emails[0].address;
|
||||
}
|
||||
else {
|
||||
check(email, String);
|
||||
|
||||
var user = Meteor.users.findOne({ "emails.address": email });
|
||||
if (!user)
|
||||
throw new Meteor.Error(403, "User not found");
|
||||
}
|
||||
|
||||
Accounts.sendLoginEmail(user._id, email);
|
||||
}});
|
||||
|
||||
/**
|
||||
|
@ -84,17 +100,20 @@ Accounts.sendLoginEmail = function (userId, address) {
|
|||
Email.send(options);
|
||||
};
|
||||
|
||||
Accounts.emailTemplates.loginNoPassword = {
|
||||
subject: function(user) {
|
||||
return "Login on " + Accounts.emailTemplates.siteName;
|
||||
},
|
||||
text: function(user, url) {
|
||||
var greeting = (user.profile && user.profile.name) ?
|
||||
("Hello " + user.profile.name + ",") : "Hello,";
|
||||
return `${greeting}
|
||||
// Check for installed accounts-password dependency.
|
||||
if (Accounts.emailTemplates) {
|
||||
Accounts.emailTemplates.loginNoPassword = {
|
||||
subject: function(user) {
|
||||
return "Login on " + Accounts.emailTemplates.siteName;
|
||||
},
|
||||
text: function(user, url) {
|
||||
var greeting = (user.profile && user.profile.name) ?
|
||||
("Hello " + user.profile.name + ",") : "Hello,";
|
||||
return `${greeting}
|
||||
To login, simply click the link below.
|
||||
${url}
|
||||
Thanks.
|
||||
`;
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -22,6 +22,11 @@ export function getLoginServices() {
|
|||
// requires it.
|
||||
this.getLoginServices = getLoginServices;
|
||||
|
||||
export function hasPasswordService() {
|
||||
// First look for OAuth services.
|
||||
return !!Package['accounts-password'];
|
||||
};
|
||||
|
||||
export function loginResultCallback(redirect, error) {
|
||||
if (Meteor.isClient) {
|
||||
if (typeof redirect === 'string'){
|
||||
|
@ -35,7 +40,7 @@ export function loginResultCallback(redirect, error) {
|
|||
};
|
||||
|
||||
export function passwordSignupFields() {
|
||||
return Accounts.ui._options.passwordSignupFields || "EMAIL_ONLY";
|
||||
return Accounts.ui._options.passwordSignupFields || "EMAIL_ONLY_NO_PASSWORD";
|
||||
};
|
||||
|
||||
export function validatePassword(password){
|
||||
|
@ -63,3 +68,9 @@ export function redirect(redirect) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function capitalize(string) {
|
||||
return string.replace(/\-/, ' ').split(' ').map(word => {
|
||||
return word.charAt(0).toUpperCase() + word.slice(1);
|
||||
}).join(' ');
|
||||
}
|
||||
|
|
|
@ -4,5 +4,11 @@ T9n.map('en', {
|
|||
'Enter newPassword': 'Enter new password',
|
||||
'Enter email': 'Enter email',
|
||||
'Enter username': 'Enter username',
|
||||
'Enter username or email': 'Enter username or email'
|
||||
'Enter username or email': 'Enter username or email',
|
||||
error: {
|
||||
accounts: {
|
||||
"Invalid email or username": "Invalid email or username"
|
||||
}
|
||||
},
|
||||
'or use': 'Or use'
|
||||
});
|
||||
|
|
|
@ -4,5 +4,11 @@ T9n.map('sv', {
|
|||
'Enter newPassword': 'Mata in nytt lösenord',
|
||||
'Enter email': 'Mata in e-post',
|
||||
'Enter username': 'Mata in användarnamn',
|
||||
'Enter username or email': 'Mata in användarnamn eller e-post'
|
||||
'Enter username or email': 'Mata in användarnamn eller e-post',
|
||||
error: {
|
||||
accounts: {
|
||||
"Invalid email or username": "Ogiltig e-postadress eller användarnamn"
|
||||
}
|
||||
},
|
||||
'or use': 'Eller använd'
|
||||
});
|
||||
|
|
|
@ -1,20 +1,31 @@
|
|||
import React from 'react';
|
||||
import {Accounts} from 'meteor/accounts-base';
|
||||
import './SocialButtons.jsx';
|
||||
import './Fields.jsx';
|
||||
import './Buttons.jsx';
|
||||
import './FormMessage.jsx';
|
||||
import './PasswordOrService.jsx';
|
||||
import './SocialButtons.jsx';
|
||||
|
||||
export class Form extends React.Component {
|
||||
render() {
|
||||
const { oauthServices, fields, buttons, error, message, ready = true, className } = this.props;
|
||||
const {
|
||||
hasPasswordService,
|
||||
oauthServices,
|
||||
fields,
|
||||
buttons,
|
||||
error,
|
||||
message,
|
||||
ready = true,
|
||||
className
|
||||
} = this.props;
|
||||
return (
|
||||
<form className={[className, ready ? "ready" : null].join(' ')}
|
||||
onSubmit={ evt => evt.preventDefault() } className="accounts-ui">
|
||||
<Accounts.ui.SocialButtons oauthServices={ oauthServices } />
|
||||
<Accounts.ui.Fields fields={ fields } />
|
||||
<Accounts.ui.Buttons buttons={ buttons } />
|
||||
<Accounts.ui.FormMessage message={ message } />
|
||||
<Accounts.ui.PasswordOrService oauthServices={ oauthServices } />
|
||||
<Accounts.ui.SocialButtons oauthServices={ oauthServices } />
|
||||
<Accounts.ui.FormMessage {...message} />
|
||||
</form>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -10,7 +10,9 @@ import {
|
|||
passwordSignupFields,
|
||||
validatePassword,
|
||||
loginResultCallback,
|
||||
getLoginServices
|
||||
getLoginServices,
|
||||
hasPasswordService,
|
||||
capitalize
|
||||
} from '../../helpers.js';
|
||||
|
||||
export class LoginForm extends Tracker.Component {
|
||||
|
@ -155,9 +157,12 @@ export class LoginForm extends Tracker.Component {
|
|||
const loginFields = [];
|
||||
const { formState } = this.state;
|
||||
|
||||
if (formState == STATES.SIGN_IN) {
|
||||
if (_.contains(["USERNAME_AND_EMAIL", "USERNAME_AND_OPTIONAL_EMAIL"],
|
||||
passwordSignupFields())) {
|
||||
if (hasPasswordService() && formState == STATES.SIGN_IN) {
|
||||
if (_.contains([
|
||||
"USERNAME_AND_EMAIL",
|
||||
"USERNAME_AND_OPTIONAL_EMAIL",
|
||||
"USERNAME_AND_EMAIL_NO_PASSWORD"
|
||||
], passwordSignupFields())) {
|
||||
loginFields.push(this.getUsernameOrEmailField());
|
||||
}
|
||||
|
||||
|
@ -165,30 +170,48 @@ export class LoginForm extends Tracker.Component {
|
|||
loginFields.push(this.getUsernameField());
|
||||
}
|
||||
|
||||
if (_.contains(["EMAIL_ONLY", "NO_PASSWORD"], passwordSignupFields())) {
|
||||
if (_.contains([
|
||||
"EMAIL_ONLY",
|
||||
"EMAIL_ONLY_NO_PASSWORD"
|
||||
], passwordSignupFields())) {
|
||||
loginFields.push(this.getEmailField());
|
||||
}
|
||||
|
||||
if (passwordSignupFields() !== "NO_PASSWORD") {
|
||||
if (!_.contains([
|
||||
"EMAIL_ONLY_NO_PASSWORD",
|
||||
"USERNAME_AND_EMAIL_NO_PASSWORD"
|
||||
], passwordSignupFields())) {
|
||||
loginFields.push(this.getPasswordField());
|
||||
}
|
||||
}
|
||||
|
||||
if (formState == STATES.SIGN_UP) {
|
||||
if (_.contains(["USERNAME_AND_EMAIL", "USERNAME_AND_OPTIONAL_EMAIL", "USERNAME_ONLY"],
|
||||
passwordSignupFields())) {
|
||||
if (hasPasswordService() && formState == STATES.SIGN_UP) {
|
||||
if (_.contains([
|
||||
"USERNAME_AND_EMAIL",
|
||||
"USERNAME_AND_OPTIONAL_EMAIL",
|
||||
"USERNAME_ONLY",
|
||||
"USERNAME_AND_EMAIL_NO_PASSWORD"
|
||||
], passwordSignupFields())) {
|
||||
loginFields.push(this.getUsernameField());
|
||||
}
|
||||
|
||||
if (_.contains(["USERNAME_AND_EMAIL", "EMAIL_ONLY", "NO_PASSWORD"], passwordSignupFields())) {
|
||||
loginFields.push(Object.assign(this.getEmailField()));
|
||||
if (_.contains([
|
||||
"USERNAME_AND_EMAIL",
|
||||
"EMAIL_ONLY",
|
||||
"EMAIL_ONLY_NO_PASSWORD",
|
||||
"USERNAME_AND_EMAIL_NO_PASSWORD"
|
||||
], passwordSignupFields())) {
|
||||
loginFields.push(this.getEmailField());
|
||||
}
|
||||
|
||||
if (passwordSignupFields() !== "USERNAME_AND_OPTIONAL_EMAIL") {
|
||||
if (_.contains(["USERNAME_AND_OPTIONAL_EMAIL"], passwordSignupFields())) {
|
||||
loginFields.push(Object.assign(this.getEmailField(), {required: false}));
|
||||
}
|
||||
|
||||
if (passwordSignupFields() !== "NO_PASSWORD") {
|
||||
if (!_.contains([
|
||||
"EMAIL_ONLY_NO_PASSWORD",
|
||||
"USERNAME_AND_EMAIL_NO_PASSWORD"
|
||||
], passwordSignupFields())) {
|
||||
loginFields.push(this.getPasswordField());
|
||||
}
|
||||
}
|
||||
|
@ -261,9 +284,10 @@ export class LoginForm extends Tracker.Component {
|
|||
loginButtons.push({
|
||||
id: 'signUp',
|
||||
label: T9n.get('signUp'),
|
||||
type: 'submit',
|
||||
type: hasPasswordService() ? 'submit' : 'link',
|
||||
className: 'active',
|
||||
disabled: waiting,
|
||||
onClick: this.signUp.bind(this)
|
||||
onClick: hasPasswordService() ? this.signUp.bind(this) : null
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -271,9 +295,10 @@ export class LoginForm extends Tracker.Component {
|
|||
loginButtons.push({
|
||||
id: 'signIn',
|
||||
label: T9n.get('signIn'),
|
||||
type: 'submit',
|
||||
type: hasPasswordService() ? 'submit' : 'link',
|
||||
className: 'active',
|
||||
disabled: waiting,
|
||||
onClick: this.signIn.bind(this)
|
||||
onClick: hasPasswordService() ? this.signIn.bind(this) : null
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -306,6 +331,8 @@ export class LoginForm extends Tracker.Component {
|
|||
|
||||
// Sort the button array so that the submit button always comes first.
|
||||
loginButtons.sort((a, b) => {
|
||||
return a.label.localeCompare(b.label);
|
||||
}).sort((a, b) => {
|
||||
return (b.type == 'submit') - (a.type == 'submit');
|
||||
});
|
||||
|
||||
|
@ -383,7 +410,7 @@ export class LoginForm extends Tracker.Component {
|
|||
return;
|
||||
}
|
||||
else {
|
||||
if (passwordSignupFields() === "NO_PASSWORD") {
|
||||
if (_.contains([ "EMAIL_ONLY_NO_PASSWORD" ], passwordSignupFields())) {
|
||||
this.loginWithoutPassword();
|
||||
return;
|
||||
}
|
||||
|
@ -397,6 +424,10 @@ export class LoginForm extends Tracker.Component {
|
|||
return;
|
||||
}
|
||||
else {
|
||||
if (_.contains([ "USERNAME_AND_EMAIL_NO_PASSWORD" ], passwordSignupFields())) {
|
||||
this.loginWithoutPassword();
|
||||
return;
|
||||
}
|
||||
loginSelector = usernameOrEmail;
|
||||
}
|
||||
}
|
||||
|
@ -425,7 +456,7 @@ export class LoginForm extends Tracker.Component {
|
|||
Accounts.oauth.serviceNames().map((service) => {
|
||||
oauthButtons.push({
|
||||
id: service,
|
||||
label: service,
|
||||
label: capitalize(service),
|
||||
disabled: waiting,
|
||||
type: 'submit',
|
||||
onClick: this.oauthSignIn.bind(this, service)
|
||||
|
@ -435,6 +466,7 @@ export class LoginForm extends Tracker.Component {
|
|||
}
|
||||
return _.indexBy(oauthButtons, 'id');
|
||||
}
|
||||
|
||||
oauthSignIn(service) {
|
||||
const { formState, waiting, user } = this.state;
|
||||
//Thanks Josh Owens for this one.
|
||||
|
@ -482,7 +514,10 @@ export class LoginForm extends Tracker.Component {
|
|||
}
|
||||
}
|
||||
|
||||
if (passwordSignupFields() === "NO_PASSWORD") {
|
||||
if (_.contains([
|
||||
"EMAIL_ONLY_NO_PASSWORD",
|
||||
"USERNAME_AND_EMAIL_NO_PASSWORD"
|
||||
], passwordSignupFields())) {
|
||||
// Generate a random password.
|
||||
options.password = Meteor.uuid();
|
||||
}
|
||||
|
@ -534,6 +569,7 @@ export class LoginForm extends Tracker.Component {
|
|||
loginWithoutPassword(){
|
||||
const {
|
||||
email = '',
|
||||
usernameOrEmail = '',
|
||||
waiting
|
||||
} = this.state;
|
||||
|
||||
|
@ -555,8 +591,27 @@ export class LoginForm extends Tracker.Component {
|
|||
this.setState({ waiting: false });
|
||||
});
|
||||
}
|
||||
else if (this.validateUsername(usernameOrEmail)) {
|
||||
this.setState({ waiting: true });
|
||||
|
||||
Accounts.loginWithoutPassword({ email: usernameOrEmail, username: usernameOrEmail }, (error) => {
|
||||
if (error) {
|
||||
this.showMessage(T9n.get(`error.accounts.${error.reason}`) || T9n.get("Unknown error"), 'error');
|
||||
}
|
||||
else {
|
||||
this.showMessage(T9n.get("info.emailSent"), 'success', 5000);
|
||||
}
|
||||
|
||||
this.setState({ waiting: false });
|
||||
});
|
||||
}
|
||||
else {
|
||||
this.showMessage(T9n.get("error.accounts.Invalid email"), 'warning');
|
||||
if (_.contains([ "USERNAME_AND_EMAIL_NO_PASSWORD" ], passwordSignupFields())) {
|
||||
this.showMessage(T9n.get("error.accounts.Invalid email or username"), 'warning');
|
||||
}
|
||||
else {
|
||||
this.showMessage(T9n.get("error.accounts.Invalid email"), 'warning');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
28
imports/ui/components/PasswordOrService.jsx
Normal file
28
imports/ui/components/PasswordOrService.jsx
Normal file
|
@ -0,0 +1,28 @@
|
|||
import React from 'react';
|
||||
import { Accounts } from 'meteor/accounts-base';
|
||||
import { T9n } from 'meteor/softwarerero:accounts-t9n';
|
||||
|
||||
export class PasswordOrService extends React.Component {
|
||||
render () {
|
||||
let {
|
||||
oauthServices = {},
|
||||
className,
|
||||
style = {}
|
||||
} = this.props;
|
||||
let labels = Object.keys(oauthServices).map(service => oauthServices[service].label);
|
||||
if (labels.length > 2) {
|
||||
labels = [];
|
||||
}
|
||||
|
||||
if (labels.length) {
|
||||
return (
|
||||
<div style={ style } className={ className }>
|
||||
{ `${T9n.get('or use')} ${ labels.join(' / ') }` }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
Accounts.ui.PasswordOrService = PasswordOrService;
|
|
@ -16,5 +16,4 @@ export class SocialButtons extends React.Component {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
Accounts.ui.SocialButtons = SocialButtons;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
Package.describe({
|
||||
name: 'studiointeract:react-accounts-ui',
|
||||
version: '1.0.20',
|
||||
version: '1.0.21',
|
||||
summary: 'Accounts UI for React in Meteor 1.3',
|
||||
git: 'https://github.com/studiointeract/react-accounts-ui',
|
||||
documentation: 'README.md'
|
||||
|
|
Loading…
Add table
Reference in a new issue