remove account.name, use email instead. special handling for primary email contact. Co-authored-by: Jonathan Cobb <jonathan@kyuss.org> Reviewed-on: #21tags/v0.11.2
@@ -11,8 +11,8 @@ import bubble.model.app.BubbleApp; | |||
import bubble.model.app.config.AppDataDriverBase; | |||
import bubble.model.app.config.AppDataView; | |||
import bubble.model.device.Device; | |||
import bubble.rule.TrafficRecord; | |||
import bubble.rule.analytics.TrafficAnalyticsRuleDriver; | |||
import bubble.rule.analytics.TrafficRecord; | |||
import lombok.Getter; | |||
import lombok.extern.slf4j.Slf4j; | |||
import org.cobbzilla.util.network.NetworkUtil; | |||
@@ -125,7 +125,7 @@ public class TrafficAnalyticsAppDataDriver extends AppDataDriverBase { | |||
+ fileExt(TYPICAL_WEB_TYPES[nextInt(0, TYPICAL_WEB_TYPES.length)])) | |||
.setReferer("NONE") | |||
.setAccountUuid(caller.getUuid()) | |||
.setAccountName(caller.getName()) | |||
.setAccountEmail(caller.getEmail()) | |||
.setDeviceUuid(device.getUuid()) | |||
.setDeviceName(device.getName()), | |||
getRecentTraffic()); | |||
@@ -32,7 +32,6 @@ public interface EmailServiceDriver extends AuthenticationDriver { | |||
final RenderedEmail email = new RenderedEmail(ctx); | |||
email.setToEmail(contact.getInfo()); | |||
email.setToName(account.getName()); | |||
email.setFromEmail(AuthenticationDriver.render("fromEmail", ctx, message, configuration, templatePath)); | |||
email.setFromName(AuthenticationDriver.render("fromName", ctx, message, configuration, templatePath)); | |||
email.setSubject(AuthenticationDriver.render("subject", ctx, message, configuration, templatePath)); | |||
@@ -29,7 +29,7 @@ public class FirstMonthFreePaymentDriver extends PromotionalPaymentDriverBase<Pr | |||
// does the caller already have one of these? | |||
final List<AccountPaymentMethod> existingCreditPaymentMethods = paymentMethodDAO.findByAccountAndCloud(caller.getUuid(), promo.getCloud()); | |||
if (!empty(existingCreditPaymentMethods)) { | |||
log.warn("applyPromo: promo="+promo.getName()+", account="+caller.getName()+", account already has one of these promos applied"); | |||
log.warn("applyPromo: promo="+promo.getName()+", account="+caller.getEmail()+", account already has one of these promos applied"); | |||
return true; // promo has already been applied, return true | |||
} | |||
paymentMethodDAO.create(new AccountPaymentMethod() | |||
@@ -41,7 +41,7 @@ public class ReferralMonthFreePaymentDriver extends PromotionalPaymentDriverBase | |||
// caller must not have any bills | |||
final int billCount = billDAO.countByAccount(caller.getUuid()); | |||
if (billCount != 0) { | |||
log.warn("applyReferralPromo: promo="+promo.getName()+", account="+caller.getName()+", account must have no Bills, found "+billCount+" bills"); | |||
log.warn("applyReferralPromo: promo="+promo.getName()+", account="+caller.getEmail()+", account must have no Bills, found "+billCount+" bills"); | |||
return false; | |||
} | |||
@@ -51,7 +51,7 @@ public class ReferralMonthFreePaymentDriver extends PromotionalPaymentDriverBase | |||
// It's OK for the referredFrom user to have many of these, as long as there is not one for this user | |||
for (AccountPaymentMethod apm : referredFromCreditPaymentMethods) { | |||
if (apm.getPaymentInfo().equals(caller.getUuid())) { | |||
log.error("applyReferralPromo: promo="+promo.getName()+", account="+caller.getName()+", referredFrom="+referredFrom.getName()+" has already referred this caller"); | |||
log.error("applyReferralPromo: promo="+promo.getName()+", account="+caller.getEmail()+", referredFrom="+referredFrom.getEmail()+" has already referred this caller"); | |||
return false; | |||
} | |||
} | |||
@@ -59,18 +59,18 @@ public class ReferralMonthFreePaymentDriver extends PromotionalPaymentDriverBase | |||
// does the caller already have one of these? | |||
final List<AccountPaymentMethod> existingCreditPaymentMethods = paymentMethodDAO.findByAccountAndCloud(caller.getUuid(), promo.getCloud()); | |||
if (!empty(existingCreditPaymentMethods)) { | |||
log.warn("applyReferralPromo: promo="+promo.getName()+", account="+caller.getName()+", account already has one of these promos applied"); | |||
log.warn("applyReferralPromo: promo="+promo.getName()+", account="+caller.getEmail()+", account already has one of these promos applied"); | |||
return true; // promo has already been applied, return true | |||
} | |||
// sanity check before using | |||
final ReferralCode ref = referralCodeDAO.findByUuid(referralCode.getUuid()); | |||
if (ref == null) { | |||
log.warn("applyReferralPromo: promo="+promo.getName()+", account="+caller.getName()+", referralCode "+referralCode.getUuid()+" was not found"); | |||
log.warn("applyReferralPromo: promo="+promo.getName()+", account="+caller.getEmail()+", referralCode "+referralCode.getUuid()+" was not found"); | |||
return false; | |||
} | |||
if (ref.claimed()) { | |||
log.warn("applyReferralPromo: promo="+promo.getName()+", account="+caller.getName()+", referralCode "+referralCode.getUuid()+" has already been claimed by "+ref.getClaimedByUuid()); | |||
log.warn("applyReferralPromo: promo="+promo.getName()+", account="+caller.getEmail()+", referralCode "+referralCode.getUuid()+" has already been claimed by "+ref.getClaimedByUuid()); | |||
return false; | |||
} | |||
@@ -5,6 +5,7 @@ | |||
package bubble.dao.account; | |||
import bubble.cloud.CloudServiceDriver; | |||
import bubble.cloud.CloudServiceType; | |||
import bubble.cloud.compute.ComputeNodeSizeType; | |||
import bubble.dao.account.message.AccountMessageDAO; | |||
import bubble.dao.app.*; | |||
@@ -46,6 +47,7 @@ import static bubble.model.account.AutoUpdatePolicy.EMPTY_AUTO_UPDATE_POLICY; | |||
import static bubble.server.BubbleConfiguration.getDEFAULT_LOCALE; | |||
import static java.lang.Thread.currentThread; | |||
import static java.util.concurrent.TimeUnit.MINUTES; | |||
import static org.cobbzilla.util.daemon.ZillaRuntime.bool; | |||
import static org.cobbzilla.util.daemon.ZillaRuntime.daemon; | |||
import static org.cobbzilla.wizard.model.IdentifiableBase.CTIME_ASC; | |||
import static org.cobbzilla.wizard.resources.ResourceUtil.invalidEx; | |||
@@ -75,18 +77,25 @@ public class AccountDAO extends AbstractCRUDDAO<Account> implements SqlViewSearc | |||
@Autowired private ReferralCodeDAO referralCodeDAO; | |||
public Account newAccount(Request req, Account caller, AccountRegistration request, Account parent) { | |||
final AccountContact contact = new AccountContact() | |||
.setType(CloudServiceType.email) | |||
.setInfo(request.getEmail()) | |||
.setRemovable(false) | |||
.setReceiveInformationalMessages(bool(request.getReceiveInformationalMessages())) | |||
.setReceivePromotionalMessages(bool(request.getReceivePromotionalMessages())); | |||
return create(new Account(request) | |||
.setAdmin(caller != null && caller.admin() && request.admin()) // only admins can create other admins | |||
.setRemoteHost(getRemoteHost(req)) | |||
.setParent(parent.getUuid()) | |||
.setPolicy(new AccountPolicy().setContact(request.getContact()))); | |||
.setPolicy(new AccountPolicy().setContact(contact, null, configuration))); | |||
} | |||
public Account findByName(String name) { return findByUniqueField("name", name); } | |||
public Account findByEmail(String email) { return findByUniqueField("email", email.trim()); } | |||
public Account findById(String id) { | |||
final Account found = findByUuid(id); | |||
return found != null ? found : findByUniqueField("name", id); | |||
return found != null ? found : findByUniqueField("email", id); | |||
} | |||
@Override public Object preCreate(Account account) { | |||
@@ -97,11 +106,13 @@ public class AccountDAO extends AbstractCRUDDAO<Account> implements SqlViewSearc | |||
if (plan != null && plan.hasMaxAccounts()) { | |||
final int numAccounts = countNotDeleted(); | |||
if (numAccounts >= plan.getMaxAccounts()) { | |||
throw invalidEx("err.name.planMaxAccountLimit", "Account limit for plan reached (max "+plan.getMaxAccounts()+" accounts)"); | |||
throw invalidEx("err.plan.planMaxAccountLimit", "Account limit for plan reached (max "+plan.getMaxAccounts()+" accounts)"); | |||
} | |||
} | |||
final ValidationResult result = account.validateName(); | |||
// if activated is false, then we are creating the root account | |||
// do not validate, it's not a valid email address and that is ok. | |||
final ValidationResult result = !activated() ? new ValidationResult() : account.validateEmail(); | |||
if (result.isInvalid()) throw invalidEx(result); | |||
if (account.getAutoUpdatePolicy() == null) { | |||
@@ -40,9 +40,7 @@ import javax.persistence.Embedded; | |||
import javax.persistence.Entity; | |||
import javax.persistence.Transient; | |||
import javax.validation.constraints.Size; | |||
import java.util.Arrays; | |||
import java.util.List; | |||
import java.util.regex.Pattern; | |||
import static bubble.ApiConstants.ACCOUNTS_ENDPOINT; | |||
import static bubble.server.BubbleConfiguration.getDEFAULT_LOCALE; | |||
@@ -50,6 +48,7 @@ import static java.util.concurrent.TimeUnit.MILLISECONDS; | |||
import static java.util.concurrent.TimeUnit.SECONDS; | |||
import static org.cobbzilla.util.daemon.ZillaRuntime.*; | |||
import static org.cobbzilla.util.reflect.ReflectionUtil.copy; | |||
import static org.cobbzilla.util.string.ValidationRegexes.EMAIL_PATTERN; | |||
import static org.cobbzilla.util.system.Sleep.sleep; | |||
import static org.cobbzilla.util.time.TimeUtil.formatDuration; | |||
import static org.cobbzilla.wizard.model.crypto.EncryptedTypes.ENCRYPTED_STRING; | |||
@@ -60,8 +59,8 @@ import static org.cobbzilla.wizard.model.entityconfig.annotations.ECForeignKeySe | |||
import static org.cobbzilla.wizard.resources.ResourceUtil.invalidEx; | |||
@ECType(root=true) | |||
@ECTypeURIs(baseURI=ACCOUNTS_ENDPOINT, listFields={"name", "url", "description", "admin", "suspended"}, isDeleteDefined=false) | |||
@ECTypeChildren(uriPrefix=ACCOUNTS_ENDPOINT+"/{Account.name}", value={ | |||
@ECTypeURIs(baseURI=ACCOUNTS_ENDPOINT, listFields={"email", "url", "description", "admin", "suspended"}, isDeleteDefined=false) | |||
@ECTypeChildren(uriPrefix=ACCOUNTS_ENDPOINT+"/{Account.email}", value={ | |||
@ECTypeChild(type=Device.class, backref="account"), | |||
@ECTypeChild(type=RuleDriver.class, backref="account"), | |||
@ECTypeChild(type=BubbleApp.class, backref="account"), | |||
@@ -89,8 +88,7 @@ public class Account extends IdentifiableBaseParentEntity implements TokenPrinci | |||
"name", "referralCode", "termsAgreed", "preferredPlan"); | |||
public static final String ROOT_USERNAME = "root"; | |||
public static final int NAME_MIN_LENGTH = 4; | |||
public static final int NAME_MAX_LENGTH = 100; | |||
public static final int EMAIL_MAX_LENGTH = 100; | |||
public static Account sageMask(Account sage) { | |||
final Account masked = new Account(sage) | |||
@@ -106,22 +104,14 @@ public class Account extends IdentifiableBaseParentEntity implements TokenPrinci | |||
public static String accountField(String table) { return table.equalsIgnoreCase("account") ? UUID : "account"; } | |||
@ECSearchable(filter=true) @ECField(index=10) | |||
@HasValue(message="err.name.required") | |||
@ECIndex(unique=true) @Column(nullable=false, updatable=false, length=NAME_MAX_LENGTH) | |||
@Getter private String name; | |||
public Account setName (String n) { this.name = n == null ? null : n.toLowerCase(); return this; } | |||
public boolean hasName () { return !empty(name); } | |||
@HasValue(message="err.email.required") | |||
@ECIndex(unique=true) @Column(nullable=false, updatable=false, length=EMAIL_MAX_LENGTH) | |||
@Getter private String email; | |||
public Account setEmail(String n) { this.email = n == null ? null : n.toLowerCase().trim(); return this; } | |||
public boolean hasEmail() { return !empty(email); } | |||
public static final Pattern VALID_NAME_PATTERN = Pattern.compile("^[A-Za-z][-\\.A-Za-z0-9_]+$"); | |||
public boolean hasInvalidName() { return hasName() && !VALID_NAME_PATTERN.matcher(getName()).matches(); } | |||
private static final List<String> RESERVED_NAMES = Arrays.asList( | |||
"root", "postmaster", "hostmaster", "webmaster", | |||
"dns", "dnscrypt", "dnscrypt-proxy", "ftp", "www", "www-data", "postgres", "ipfs", | |||
"redis", "nginx", "mitmproxy", "mitmdump", "algo", "algovpn"); | |||
public boolean hasReservedName () { return hasName() && isReservedName(getName()); } | |||
public static boolean isReservedName(String name) { return RESERVED_NAMES.contains(name); } | |||
@Override @Transient public String getName() { return getEmail(); } | |||
public Account setName(String n) { return setEmail(n); } | |||
// make this updatable if we ever want accounts to be able to change parents | |||
// there might be a lot more involved in that action though (read-only parent objects that will no longer be visible, must be copied in?) | |||
@@ -244,7 +234,7 @@ public class Account extends IdentifiableBaseParentEntity implements TokenPrinci | |||
public Account(Account other) { copy(this, other, CREATE_FIELDS); } | |||
public Account(ActivationRequest request) { | |||
setName(request.getName()); | |||
setEmail(request.getEmail()); | |||
setHashedPassword(new HashedPassword(request.getPassword())); | |||
setAdmin(true); | |||
setDescription(request.hasDescription() ? request.getDescription() : "root user"); | |||
@@ -253,7 +243,7 @@ public class Account extends IdentifiableBaseParentEntity implements TokenPrinci | |||
} | |||
public Account(AccountRegistration request) { | |||
setName(request.getName()); | |||
setEmail(request.getEmail()); | |||
setHashedPassword(new HashedPassword(request.getPassword())); | |||
setTermsAgreed(request.getTermsAgreed()); | |||
setPreferredPlan(request.getPreferredPlan()); | |||
@@ -289,21 +279,17 @@ public class Account extends IdentifiableBaseParentEntity implements TokenPrinci | |||
@Transient @Getter @Setter private transient String remoteHost; | |||
@Transient @JsonIgnore @Getter @Setter private transient Boolean verifyContact; | |||
public ValidationResult validateName () { | |||
public ValidationResult validateEmail() { return validateEmail(getEmail()); } | |||
public static ValidationResult validateEmail(String email) { | |||
final ValidationResult result = new ValidationResult(); | |||
if (!hasName()) { | |||
result.addViolation("err.name.required"); | |||
if (empty(email)) { | |||
result.addViolation("err.email.required"); | |||
} else { | |||
if (getName().length() < NAME_MIN_LENGTH) { | |||
result.addViolation("err.name.tooShort"); | |||
} else if (getName().length() > NAME_MAX_LENGTH) { | |||
result.addViolation("err.name.tooLong"); | |||
} | |||
if (!admin() && hasReservedName()) { | |||
result.addViolation("err.name.reserved"); | |||
} | |||
if (hasInvalidName()) { | |||
result.addViolation("err.name.regexFailed"); | |||
if (!EMAIL_PATTERN.matcher(email).matches()) { | |||
result.addViolation("err.email.invalid"); | |||
} else if (email.length() > EMAIL_MAX_LENGTH) { | |||
result.addViolation("err.email.tooLong"); | |||
} | |||
} | |||
return result; | |||
@@ -5,6 +5,7 @@ | |||
package bubble.model.account; | |||
import bubble.cloud.CloudServiceType; | |||
import bubble.dao.account.AccountDAO; | |||
import bubble.model.account.message.AccountAction; | |||
import bubble.model.account.message.AccountMessage; | |||
import bubble.model.account.message.AccountMessageType; | |||
@@ -40,7 +41,7 @@ import static org.cobbzilla.wizard.resources.ResourceUtil.invalidEx; | |||
public class AccountContact implements Serializable { | |||
public static final int MAX_NICK_LENGTH = 100; | |||
public static final String[] UPDATE_EXCLUDE_FIELDS = {UUID, "type", "info"}; | |||
public static final String[] UPDATE_EXCLUDE_FIELDS = {UUID, "type", "info", "removable"}; | |||
public AccountContact(AccountContact other) { copy(this, other); } | |||
@@ -67,12 +68,15 @@ public class AccountContact implements Serializable { | |||
@Getter @Setter private CloudServiceType type; | |||
@JsonIgnore public boolean isAuthenticator () { return type == CloudServiceType.authenticator; } | |||
@JsonIgnore public boolean isNotAuthenticator () { return !isAuthenticator(); } | |||
@JsonIgnore public boolean getIsEmail () { return type == CloudServiceType.email; } | |||
@JsonIgnore public boolean getIsSms () { return type == CloudServiceType.sms; } | |||
@JsonIgnore public boolean isEmail () { return type == CloudServiceType.email; } | |||
@JsonIgnore public boolean isSms () { return type == CloudServiceType.sms; } | |||
@Getter @Setter private Boolean verified = null; | |||
public boolean verified () { return bool(verified); } | |||
@Getter @Setter private Boolean removable = true; | |||
public boolean removable () { return bool(removable); } | |||
@Getter @Setter private Boolean requiredForNetworkOperations = true; | |||
@Getter @Setter private Boolean requiredForAccountOperations = true; | |||
@@ -107,7 +111,12 @@ public class AccountContact implements Serializable { | |||
if (!c.hasUuid()) c.initUuid(); | |||
if (contacts == null) contacts = new AccountContact[0]; | |||
if (contacts == null) { | |||
contacts = new AccountContact[0]; | |||
c.setRemovable(false); // first contact is not removable | |||
} else { | |||
c.setRemovable(true); // all other contacts are removable | |||
} | |||
final AccountContact existing = Arrays.stream(contacts).filter(contactMatch(c)).findFirst().orElse(null); | |||
if (existing != null) { | |||
// updating a contact -- cannot set authFactor if verification is still required | |||
@@ -125,6 +134,14 @@ public class AccountContact implements Serializable { | |||
existing.update(c); | |||
} else { | |||
if (c.isEmail()) { | |||
// has another user registered this as their primary email? | |||
final Account registeredWithEmail = configuration.getBean(AccountDAO.class).findByEmail(c.getInfo()); | |||
if (registeredWithEmail != null && (account == null || !registeredWithEmail.getUuid().equals(account.getUuid()))) { | |||
throw invalidEx("err.email.registered", "Email already registered"); | |||
} | |||
} | |||
// creating a new contact -- cannot set authFactor for contacts requiring verification | |||
if (c.getType().isVerifiableAuthenticationType() && c.authFactor()) { | |||
throw invalidEx("err.contact.unverified", "cannot set authFactor on an unverified contact; verify first"); | |||
@@ -164,10 +181,14 @@ public class AccountContact implements Serializable { | |||
.orElse(null); | |||
} | |||
public static AccountContact[] remove(AccountContact contact, AccountContact[] contacts) { | |||
public static AccountContact[] remove(Account account, AccountContact contact, AccountContact[] contacts) { | |||
if (contacts == null || contacts.length == 0) return contacts; | |||
if (contact.getType() == CloudServiceType.email && contact.getInfo().equals(account.getEmail())) { | |||
log.warn("remove: cannot remove account.email contact"); | |||
return contacts; | |||
} | |||
final List<AccountContact> contactList = new ArrayList<>(Arrays.asList(contacts)); | |||
return contactList.removeIf(contactMatch(contact)) | |||
return contactList.removeIf(c -> c.removable() && contactMatch(contact).test(c)) | |||
? contactList.toArray(new AccountContact[contacts.length-1]) | |||
: contacts; | |||
} | |||
@@ -0,0 +1,18 @@ | |||
package bubble.model.account; | |||
import org.cobbzilla.wizard.auth.LoginRequest; | |||
import static org.cobbzilla.util.daemon.ZillaRuntime.empty; | |||
public class AccountLoginRequest extends LoginRequest { | |||
public String getEmail () { return getName(); } | |||
public AccountLoginRequest setEmail(String email) { | |||
setName(email); | |||
return this; | |||
} | |||
public boolean hasEmail () { return !empty(getEmail()); } | |||
} |
@@ -112,13 +112,14 @@ public class AccountPolicy extends IdentifiableBase implements HasAccount { | |||
@Transient public AccountContact[] getAccountContacts () { return accountContactsJson == null ? null : json(accountContactsJson, AccountContact[].class); } | |||
public AccountPolicy setAccountContacts(AccountContact[] contacts) { return setAccountContactsJson(contacts == null ? null : json(contacts, DB_JSON_MAPPER)); } | |||
public AccountPolicy setContact(AccountContact c) { return setContact(c, null, null); } | |||
public AccountPolicy setContact(AccountContact c, Account account, BubbleConfiguration configuration) { | |||
setAccountContacts(AccountContact.set(c, getAccountContacts(), account, configuration)); | |||
return this; | |||
} | |||
public AccountPolicy removeContact(AccountContact c) { setAccountContacts(AccountContact.remove(c, getAccountContacts())); return this; } | |||
public AccountPolicy removeContact(Account account, AccountContact c) { | |||
setAccountContacts(AccountContact.remove(account, c, getAccountContacts())); | |||
return this; | |||
} | |||
public List<AccountContact> getAllowedContacts(AccountMessage message) { | |||
if (!hasAccountContacts()) return Collections.emptyList(); | |||
@@ -17,12 +17,15 @@ public class AccountRegistration extends Account { | |||
@Getter @Setter private String promoCode; | |||
public boolean hasPromoCode () { return !empty(promoCode); } | |||
@Getter @Setter private AccountContact contact; | |||
public boolean hasContact () { return contact != null; } | |||
@Getter @Setter private Boolean agreeToTerms = null; | |||
public boolean agreeToTerms () { return agreeToTerms != null && agreeToTerms; } | |||
@Getter @Setter private Boolean receiveInformationalMessages = null; | |||
public boolean receiveInformationalMessages () { return receiveInformationalMessages != null && receiveInformationalMessages; } | |||
@Getter @Setter private Boolean receivePromotionalMessages = null; | |||
public boolean receivePromotionalMessages () { return receivePromotionalMessages != null && receivePromotionalMessages; } | |||
@Getter @Setter private AccountPaymentMethod paymentMethodObject; | |||
public boolean hasPaymentMethod () { return paymentMethodObject != null; } | |||
} |
@@ -18,7 +18,7 @@ public class TotpBean { | |||
public TotpBean(GoogleAuthenticatorKey creds, Account account, BubbleConfiguration configuration) { | |||
setKey(creds.getKey()); | |||
setUrl(getOtpAuthTotpURL(configuration.getThisNetwork().getNetworkDomain(), account.getName()+"@"+configuration.getThisNetwork().getNetworkDomain(), creds)); | |||
setUrl(getOtpAuthTotpURL(configuration.getThisNetwork().getNetworkDomain(), account.getEmail()+"/"+configuration.getThisNetwork().getNetworkDomain(), creds)); | |||
setBackupCodes(creds.getScratchCodes().toArray(new Integer[0])); | |||
} | |||
@@ -4,7 +4,9 @@ | |||
*/ | |||
package bubble.model.account.message.handlers; | |||
import bubble.dao.account.AccountDAO; | |||
import bubble.dao.account.AccountPolicyDAO; | |||
import bubble.model.account.Account; | |||
import bubble.model.account.AccountContact; | |||
import bubble.model.account.AccountPolicy; | |||
import bubble.model.account.message.AccountMessage; | |||
@@ -16,6 +18,7 @@ import org.springframework.beans.factory.annotation.Autowired; | |||
@Slf4j | |||
public class AccountVerifyHandler implements AccountMessageCompletionHandler { | |||
@Autowired private AccountDAO accountDAO; | |||
@Autowired private AccountPolicyDAO policyDAO; | |||
@Override public void confirm(AccountMessage message, NameAndValue[] data) { | |||
@@ -26,10 +29,19 @@ public class AccountVerifyHandler implements AccountMessageCompletionHandler { | |||
} | |||
@Override public void deny(AccountMessage message) { | |||
final Account account = accountDAO.findByUuid(message.getAccount()); | |||
if (account == null) { | |||
log.warn("deny: account not found: "+message.getAccount()); | |||
return; | |||
} | |||
final AccountPolicy policy = policyDAO.findSingleByAccount(message.getAccount()); | |||
final String contact = message.getRequest().getContact(); | |||
final AccountContact contact = policy.findContact(new AccountContact().setUuid(message.getRequest().getContact())); | |||
if (contact == null) { | |||
log.warn("deny: contact not found in policy: "+contact); | |||
return; | |||
} | |||
log.info("deny: removing contact "+ contact +" from account "+message.getAccount()); | |||
policy.removeContact(new AccountContact().setUuid(contact)); | |||
policy.removeContact(account, contact); | |||
policyDAO.update(policy); | |||
} | |||
@@ -22,9 +22,12 @@ import static org.cobbzilla.util.daemon.ZillaRuntime.empty; | |||
@NoArgsConstructor @Accessors(chain=true) | |||
public class ActivationRequest { | |||
@HasValue(message="err.name.required") | |||
@Getter @Setter private String name; | |||
public boolean hasName () { return !empty(name); } | |||
@HasValue(message="err.email.required") | |||
@Getter @Setter private String email; | |||
public boolean hasEmail() { return !empty(email); } | |||
public String getName() { return getEmail(); } | |||
public ActivationRequest setName(String n) { return setEmail(n); } | |||
@HasValue(message="err.password.required") | |||
@Getter @Setter private String password; | |||
@@ -201,6 +201,13 @@ public class BubbleNetwork extends IdentifiableBase implements HasNetwork, HasBu | |||
return die("hostFromFqdn("+fqdn+"): expected suffix ."+getNetworkDomain()); | |||
} | |||
private static final List<String> RESERVED_NAMES = Arrays.asList( | |||
"root", "postmaster", "hostmaster", "webmaster", | |||
"dns", "dnscrypt", "dnscrypt-proxy", "ftp", "www", "www-data", "postgres", "ipfs", | |||
"redis", "nginx", "mitmproxy", "mitmdump", "algo", "algovpn"); | |||
public static boolean isReservedName(String name) { return RESERVED_NAMES.contains(name); } | |||
public static HostnameValidationResult validateHostname(HasNetwork request, | |||
AccountDAO accountDAO, | |||
BubbleNetworkDAO networkDAO) { | |||
@@ -211,7 +218,7 @@ public class BubbleNetwork extends IdentifiableBase implements HasNetwork, HasBu | |||
final String name = request.getName(); | |||
if (!validateRegexMatches(HOST_PART_PATTERN, name)) { | |||
errors.addViolation("err.name.invalid"); | |||
} else if (Account.isReservedName(name)) { | |||
} else if (isReservedName(name)) { | |||
errors.addViolation("err.name.reserved"); | |||
} else if (name.length() > NETWORK_NAME_MAXLEN) { | |||
errors.addViolation("err.name.length"); | |||
@@ -225,7 +232,7 @@ public class BubbleNetwork extends IdentifiableBase implements HasNetwork, HasBu | |||
log.info("validateHostname: name "+tryName+" is ineligible (network="+network.getUuid()+") exists"); | |||
continue; | |||
} else { | |||
final Account acct = accountDAO.findByName(tryName); | |||
final Account acct = accountDAO.findByEmail(tryName); | |||
if (acct != null && !acct.getUuid().equals(request.getAccount())) { | |||
log.info("validateHostname: name "+tryName+" is ineligible (account="+acct.getUuid()+") exists (request.account="+request.getAccount()+")"); | |||
continue; | |||
@@ -55,7 +55,7 @@ public class IdentityResource { | |||
if (caller.admin()) { | |||
// only admin can find any user | |||
found = ((AccountDAO) dao).findById(id); | |||
} else if (id.equals(caller.getUuid()) || id.equals(caller.getName())) { | |||
} else if (id.equals(caller.getUuid()) || id.equals(caller.getEmail())) { | |||
// other callers can find themselves | |||
found = caller; | |||
} else { | |||
@@ -46,8 +46,7 @@ import java.util.List; | |||
import java.util.Map; | |||
import static bubble.ApiConstants.*; | |||
import static bubble.model.account.Account.ADMIN_UPDATE_FIELDS; | |||
import static bubble.model.account.Account.validatePassword; | |||
import static bubble.model.account.Account.*; | |||
import static bubble.resources.account.AuthResource.forgotPasswordMessage; | |||
import static org.cobbzilla.util.http.HttpContentTypes.APPLICATION_JSON; | |||
import static org.cobbzilla.wizard.resources.ResourceUtil.*; | |||
@@ -86,22 +85,24 @@ public class AccountsResource { | |||
@Context ContainerRequest ctx, | |||
AccountRegistration request) { | |||
final AccountContext c = new AccountContext(ctx, request.getName(), true); | |||
final AccountContext c = new AccountContext(ctx, request.getEmail(), true); | |||
// only admins can use this endpoint | |||
// regular users must use AuthResource.register | |||
if (!c.caller.admin()) return forbidden(); | |||
if (c.account != null) return invalid("err.user.exists", "User with name "+request.getName()+" already exists", request.getName()); | |||
if (c.account != null) { | |||
// trying to create self -- just return self. tests sometimes do this | |||
if (c.account.getUuid().equals(c.caller.getUuid())) return ok(c.caller); | |||
// not self, trying to create a user that already exists, return error | |||
return invalid("err.user.exists", "User with name "+request.getEmail()+" already exists", request.getEmail()); | |||
} | |||
final ValidationResult errors = new ValidationResult(); | |||
final ConstraintViolationBean passwordViolation = validatePassword(request.getPassword()); | |||
if (passwordViolation != null) errors.addViolation(passwordViolation); | |||
if (!request.hasContact()) { | |||
errors.addViolation("err.contact.required", "No contact information provided", request.getName()); | |||
} else { | |||
request.getContact().validate(errors); | |||
} | |||
errors.addAll(validateEmail(request.getEmail())); | |||
if (!request.agreeToTerms()) { | |||
errors.addViolation("err.terms.required", "You must agree to the legal terms to use this service"); | |||
} else { | |||
@@ -283,7 +284,7 @@ public class AccountsResource { | |||
authenticatorService.ensureAuthenticated(ctx, policy, ActionTarget.account); | |||
final AccountContact contact = policy.findContact(new AccountContact().setType(type).setInfo(info)); | |||
if (contact == null) return notFound(type.name()+"/"+info); | |||
return ok(policyDAO.update(policy.removeContact(contact)).mask()); | |||
return ok(policyDAO.update(policy.removeContact(c.account, contact)).mask()); | |||
} | |||
@DELETE @Path("/{id}"+EP_POLICY+EP_CONTACTS+EP_AUTHENTICATOR) | |||
@@ -297,7 +298,7 @@ public class AccountsResource { | |||
final AccountContact contact = policy.findContact(new AccountContact().setType(CloudServiceType.authenticator)); | |||
if (contact == null) return notFound(CloudServiceType.authenticator.name()); | |||
final AccountPolicy updated = policyDAO.update(policy.removeContact(contact)).mask(); | |||
final AccountPolicy updated = policyDAO.update(policy.removeContact(c.account, contact)).mask(); | |||
authenticatorService.flush(c.caller.getToken()); | |||
return ok(updated); | |||
@@ -313,7 +314,7 @@ public class AccountsResource { | |||
final AccountContact found = policy.findContact(new AccountContact().setUuid(uuid)); | |||
if (found == null) return notFound(uuid); | |||
return ok(policyDAO.update(policy.removeContact(found)).mask()); | |||
return ok(policyDAO.update(policy.removeContact(c.account, found)).mask()); | |||
} | |||
@DELETE @Path("/{id}"+EP_REQUEST) | |||
@@ -37,7 +37,6 @@ import bubble.service.cloud.DeviceIdService; | |||
import bubble.service.notify.NotificationService; | |||
import lombok.extern.slf4j.Slf4j; | |||
import org.cobbzilla.util.collection.NameAndValue; | |||
import org.cobbzilla.wizard.auth.LoginRequest; | |||
import org.cobbzilla.wizard.stream.FileSendableResource; | |||
import org.cobbzilla.wizard.validation.ConstraintViolationBean; | |||
import org.cobbzilla.wizard.validation.SimpleViolationException; | |||
@@ -133,7 +132,7 @@ public class AuthResource { | |||
if (accountDAO.activated()) { | |||
return invalid("err.activation.alreadyDone", "activation has already been done"); | |||
} | |||
if (!request.hasName()) return invalid("err.name.required", "name is required"); | |||
if (!request.hasEmail()) return invalid("err.email.required", "email is required"); | |||
if (!request.hasPassword()) return invalid("err.password.required", "password is required"); | |||
if (request.getPassword().contains("{{") && request.getPassword().contains("}}")) { | |||
@@ -212,21 +211,15 @@ public class AuthResource { | |||
request.setAdmin(false); // cannot register admins, they must be created | |||
final ValidationResult errors = request.validateName(); | |||
final ValidationResult errors = request.validateEmail(); | |||
if (errors.isValid()) { | |||
final Account existing = accountDAO.findByName(request.getName()); | |||
if (existing != null) errors.addViolation("err.name.registered", "Name is already registered: ", request.getName()); | |||
final Account existing = accountDAO.findByEmail(request.getEmail()); | |||
if (existing != null) errors.addViolation("err.name.registered", "Name is already registered: ", request.getEmail()); | |||
} | |||
final ConstraintViolationBean passwordViolation = validatePassword(request.getPassword()); | |||
if (passwordViolation != null) errors.addViolation(passwordViolation); | |||
if (!request.hasContact()) { | |||
errors.addViolation("err.contact.required", "No contact information provided", request.getName()); | |||
} else { | |||
request.getContact().validate(errors); | |||
} | |||
if (!request.agreeToTerms()) { | |||
errors.addViolation("err.terms.required", "You must agree to the legal terms to use this service"); | |||
} else { | |||
@@ -301,21 +294,21 @@ public class AuthResource { | |||
@POST @Path(EP_LOGIN) | |||
public Response login(@Context Request req, | |||
@Context ContainerRequest ctx, | |||
LoginRequest request, | |||
AccountLoginRequest request, | |||
@QueryParam("k") String unlockKey) { | |||
if (!request.hasName()) return invalid("err.name.required", "name is required"); | |||
if (!request.hasEmail()) return invalid("err.email.required", "email is required"); | |||
if (!request.hasPassword()) return invalid("err.password.required", "password is required"); | |||
final Account account = accountDAO.findByName(request.getName()); | |||
if (account == null || account.deleted()) return notFound(request.getName()); | |||
final Account account = accountDAO.findByEmail(request.getEmail()); | |||
if (account == null || account.deleted()) return notFound(request.getEmail()); | |||
if (!account.getHashedPassword().isCorrectPassword(request.getPassword())) { | |||
return notFound(request.getName()); | |||
return notFound(request.getEmail()); | |||
} | |||
if (account.suspended()) return invalid("err.account.suspended"); | |||
boolean isUnlock = false; | |||
if (account.locked()) { | |||
if (!accountDAO.locked()) { | |||
log.info("login: account "+account.getName()+" was locked, but system is unlocked, unlocking again"); | |||
log.info("login: account "+account.getEmail()+" was locked, but system is unlocked, unlocking again"); | |||
accountDAO.unlock(); | |||
} else { | |||
@@ -360,7 +353,7 @@ public class AuthResource { | |||
); | |||
} | |||
return ok(new Account() | |||
.setName(account.getName()) | |||
.setEmail(account.getEmail()) | |||
.setLoginRequest(loginRequest != null ? loginRequest.getUuid() : null) | |||
.setMultifactorAuth(AccountContact.mask(authFactors))); | |||
} | |||
@@ -387,9 +380,9 @@ public class AuthResource { | |||
@POST @Path(EP_FORGOT_PASSWORD) | |||
public Response forgotPassword(@Context Request req, | |||
@Context ContainerRequest ctx, | |||
LoginRequest request) { | |||
if (!request.hasName()) return invalid("err.name.required"); | |||
final Account account = accountDAO.findById(request.getName()); | |||
AccountLoginRequest request) { | |||
if (!request.hasEmail()) return invalid("err.email.required"); | |||
final Account account = accountDAO.findById(request.getEmail()); | |||
if (account == null) return ok(); | |||
accountMessageDAO.create(forgotPasswordMessage(req, account, configuration)); | |||
@@ -103,9 +103,9 @@ public class MessagesResource { | |||
if (!messageCache.containsKey(cacheKey)) { | |||
final Properties props; | |||
if (isAppsGroup) { | |||
if (log.isDebugEnabled()) log.debug("loadMessages: loading app messages for caller="+caller.getName()+", locale="+locale); | |||
if (log.isDebugEnabled()) log.debug("loadMessages: loading app messages for caller="+caller.getEmail()+", locale="+locale); | |||
props = appMessageService.loadAppMessages(caller, locale); | |||
if (log.isDebugEnabled()) log.debug("loadMessages: loaded app messages for caller="+caller.getName()+", locale="+locale+", props.size="+props.size()); | |||
if (log.isDebugEnabled()) log.debug("loadMessages: loaded app messages for caller="+caller.getEmail()+", locale="+locale+", props.size="+props.size()); | |||
} else { | |||
props = new Properties(); | |||
props.load(new BufferedReader(new InputStreamReader(loadResourceAsStream(MESSAGE_RESOURCE_BASE + locale + MESSAGE_RESOURCE_PATH + group + "/" + RESOURCE_MESSAGES_PROPS), UTF8cs))); | |||
@@ -2,7 +2,7 @@ | |||
* Copyright (c) 2020 Bubble, Inc. All rights reserved. | |||
* For personal (non-commercial) use, see license: https://getbubblenow.com/bubble-license/ | |||
*/ | |||
package bubble.rule.analytics; | |||
package bubble.rule; | |||
import bubble.model.account.Account; | |||
import bubble.model.device.Device; | |||
@@ -22,7 +22,7 @@ public class TrafficRecord { | |||
@Getter @Setter private long requestTime = now(); | |||
@Getter @Setter private String action; | |||
@Getter @Setter private String accountUuid; | |||
@Getter @Setter private String accountName; | |||
@Getter @Setter private String accountEmail; | |||
@Getter @Setter private String deviceUuid; | |||
@Getter @Setter private String deviceName; | |||
@Getter @Setter private String ip; | |||
@@ -32,7 +32,7 @@ public class TrafficRecord { | |||
@Getter @Setter private String referer; | |||
public TrafficRecord(FilterMatchersRequest filter, Account account, Device device) { | |||
setAccountName(account == null ? null : account.getName()); | |||
setAccountEmail(account == null ? null : account.getEmail()); | |||
setAccountUuid(account == null ? null : account.getUuid()); | |||
setDeviceName(device == null ? null : device.getName()); | |||
setDeviceUuid(device == null ? null : device.getUuid()); | |||
@@ -10,6 +10,7 @@ import bubble.model.device.Device; | |||
import bubble.resources.stream.FilterMatchersRequest; | |||
import bubble.rule.AbstractAppRuleDriver; | |||
import bubble.rule.FilterMatchDecision; | |||
import bubble.rule.TrafficRecord; | |||
import bubble.service.stream.AppRuleHarness; | |||
import lombok.Getter; | |||
import lombok.extern.slf4j.Slf4j; | |||
@@ -60,7 +60,7 @@ public class BubbleFirstTimeListener extends RestServerLifecycleListenerBase<Bub | |||
} | |||
final AccountPolicy adminPolicy = configuration.getBean(AccountPolicyDAO.class).findSingleByAccount(adminAccount.getUuid()); | |||
if (adminPolicy == null || !adminPolicy.hasVerifiedNonAuthenticatorAccountContacts()) { | |||
log.error("onStart: no AccountPolicy found (or no verified non-authenticator contacts) for admin account (" + adminAccount.getName() + "), cannot send first time install message, unlocking now"); | |||
log.error("onStart: no AccountPolicy found (or no verified non-authenticator contacts) for admin account (" + adminAccount.getEmail() + "), cannot send first time install message, unlocking now"); | |||
accountDAO.unlock(); | |||
return; | |||
} | |||
@@ -24,7 +24,7 @@ public class DeviceInitializerListener extends RestServerLifecycleListenerBase { | |||
if (!configuration.isSageLauncher()) { | |||
for (Account a : accountDAO.findAll()) { | |||
if (!a.getName().equals(ROOT_USERNAME)) { | |||
if (!a.getEmail().equals(ROOT_USERNAME)) { | |||
deviceDAO.ensureSpareDevice(a.getUuid(), thisNode.getNetwork(), false); | |||
} | |||
} | |||
@@ -63,7 +63,7 @@ public class StandardSyncPasswordService implements SyncPasswordService { | |||
notificationService.notify(configuration.getSageNode(), sync_password, notification); | |||
} else { | |||
reportError("syncPassword("+account.getName()+"/"+account.getUuid()+"): invalid installType: "+installType); | |||
reportError("syncPassword("+account.getEmail()+"/"+account.getUuid()+"): invalid installType: "+installType); | |||
} | |||
} | |||
@@ -550,7 +550,6 @@ err.cloudServiceType.required=Cloud type is required | |||
err.cloudServiceType.invalid=Cloud type is invalid | |||
err.cloudType.invalid=Cloud type is invalid | |||
err.configJson.length=Configuration JSON is too long | |||
err.contact.required=Contact information is required | |||
err.contactType.required=Contact type is required | |||
err.contact.authenticatorAuthFactorCannotBeChanged=Authenticator is always a required auth factor | |||
err.contact.unverified=Cannot configure an unverified contact; verify first | |||
@@ -653,6 +652,7 @@ err.paymentService.notFound=Payment service is invalid | |||
err.parent.notFound=Parent account does not exist | |||
err.path.length=Path is too long | |||
err.plan.required=Plan is required | |||
err.plan.planMaxAccountLimit=No more accounts can be created. Please upgrade your plan to create more accounts. | |||
err.price.invalid=Price is invalid | |||
err.price.length=Price is too long | |||
err.privateKeyHash.length=Private key hash is too long | |||
@@ -197,9 +197,10 @@ err.registration.disabled=Account registration is not enabled on this Bubble | |||
err.register.alreadyLoggedIn=Cannot register a new account when logged in | |||
err.name.registered=Username is already registered | |||
err.contactType.required=Contact type is required | |||
err.contact.required=No contact information provided | |||
err.email.required=Email is required | |||
err.email.invalid=Email is invalid | |||
err.email.tooLong=Email is too long | |||
err.email.registered=Email is already registered | |||
err.phone.required=SMS Phone is required | |||
err.phone.invalid=SMS Phone is invalid | |||
err.phone.length=SMS Phone is too long | |||
@@ -116,7 +116,7 @@ public abstract class ActivatedBubbleModelTestBase extends BubbleModelTestBase { | |||
// if DB already exists, server has already been activated | |||
try { | |||
admin = client.put(AUTH_ENDPOINT + EP_ACTIVATE, new ActivationRequest() | |||
.setName(ROOT_USERNAME) | |||
.setEmail(ROOT_USERNAME) | |||
.setPassword(ROOT_PASSWORD) | |||
.setNetworkName(hostname_short()) | |||
.addCloudConfig(dns) | |||
@@ -3,7 +3,6 @@ | |||
"comment": "declare default parameters for new_account test part", | |||
"include": "_defaults", | |||
"params": { | |||
"username": "user-{{rand 10}}", | |||
"password": "foobar1!", | |||
"email": "user-{{rand 5}}@example.com", | |||
"rootSessionName": "rootSession", | |||
@@ -21,11 +20,10 @@ | |||
"uri": "users", | |||
"method": "put", | |||
"entity": { | |||
"name": "<<username>>", | |||
"email": "<<email>>", | |||
"password": "<<password>>", | |||
"admin": "<<admin>>", | |||
"agreeToTerms": true, | |||
"contact": {"type": "email", "info": "<<email>>"} | |||
"agreeToTerms": true | |||
} | |||
}, | |||
"response": { | |||
@@ -56,7 +54,7 @@ | |||
"session": "new", | |||
"uri": "auth/login", | |||
"entity": { | |||
"name": "<<username>>", | |||
"name": "<<email>>", | |||
"password": "<<password>>" | |||
} | |||
}, | |||
@@ -96,7 +94,7 @@ | |||
"request": { | |||
"session": "new", | |||
"uri": "auth/approve/{{userInbox.[0].ctx.confirmationToken}}", | |||
"entity": [{"name": "account", "value": "<<username>>"}] | |||
"entity": [{"name": "account", "value": "<<email>>"}] | |||
} | |||
}, | |||
@@ -19,10 +19,9 @@ | |||
"session": "new", | |||
"uri": "auth/register", | |||
"entity": { | |||
"name": "<<referredName>>", | |||
"name": "<<referredName>>@example.com", | |||
"password": "password1!", | |||
"agreeToTerms": true, | |||
"contact": {"type": "email", "info": "<<referredName>>@example.com"}, | |||
"promoCode": "<<referralCode>>" | |||
} | |||
}, | |||
@@ -1,6 +1,6 @@ | |||
[ | |||
{ | |||
"name": "root", | |||
"email": "root", | |||
"children": { | |||
"Device": [ {"name": "test"} ] | |||
} | |||
@@ -3,8 +3,6 @@ | |||
"comment": "activate service, create account, login for block_delete", | |||
"include": "new_account", | |||
"params": { | |||
"username": "user-to-partially-delete", | |||
"password": "foobar1!", | |||
"email": "user-partially-account-deletion@example.com", | |||
"verifyEmail": "true" | |||
} | |||
@@ -5,10 +5,9 @@ | |||
"uri": "users", | |||
"method": "put", | |||
"entity": { | |||
"name": "user_with_payment_to_block_delete", | |||
"name": "user_with_payment_to_block_delete@example.com", | |||
"password": "password1!", | |||
"agreeToTerms": true, | |||
"contact": { "type": "email", "info": "user_with_payment_to_block_delete@example.com" } | |||
"agreeToTerms": true | |||
} | |||
}, | |||
"response": { "store": "testAccount" } | |||
@@ -3,8 +3,6 @@ | |||
"comment": "activate service, create account, login", | |||
"include": "new_account", | |||
"params": { | |||
"username": "user-to-delete", | |||
"password": "foobar1!", | |||
"email": "user-account-deletion@example.com", | |||
"verifyEmail": "true" | |||
} | |||
@@ -42,7 +40,7 @@ | |||
{ | |||
"comment": "lookup user - just checking it is the one for deletion", | |||
"request": { "uri": "users/{{user.uuid}}" }, | |||
"response": { "check": [{ "condition": "json.getName() == 'user-to-delete'" }] } | |||
"response": { "check": [{ "condition": "json.getName() == 'user-account-deletion@example.com'" }] } | |||
}, | |||
{ | |||
@@ -5,10 +5,9 @@ | |||
"uri": "users", | |||
"method": "put", | |||
"entity": { | |||
"name": "user_with_payment_to_delete", | |||
"name": "user_with_payment_to_delete@example.com", | |||
"password": "password1!", | |||
"agreeToTerms": true, | |||
"contact": { "type": "email", "info": "user_with_payment_to_delete@example.com" } | |||
"agreeToTerms": true | |||
} | |||
}, | |||
"response": { "store": "testAccount" } | |||
@@ -3,7 +3,6 @@ | |||
"comment": "activate service, create account, login", | |||
"include": "new_account", | |||
"params": { | |||
"password": "foobar1!", | |||
"email": "user-account-crud@example.com", | |||
"verifyEmail": "true" | |||
} | |||
@@ -14,9 +14,8 @@ | |||
"session": "new", | |||
"uri": "auth/register", | |||
"entity": { | |||
"name": "foobar1", | |||
"password": "password1!", | |||
"contact": { "type": "email", "info": "user-{{rand 5}}@example.com" } | |||
"name": "account_reg_user@example.com", | |||
"password": "password1!" | |||
} | |||
}, | |||
"response": { | |||
@@ -31,10 +30,9 @@ | |||
"session": "new", | |||
"uri": "auth/register", | |||
"entity": { | |||
"name": "foobar1", | |||
"name": "account_reg_user@example.com", | |||
"password": "password1!", | |||
"agreeToTerms": true, | |||
"contact": { "type": "email", "info": "user-{{rand 5}}@example.com" } | |||
"agreeToTerms": true | |||
} | |||
}, | |||
"response": { | |||
@@ -47,7 +45,7 @@ | |||
"comment": "view current user", | |||
"request": { "uri": "me" }, | |||
"response": { | |||
"check": [ {"condition": "json.getName() == 'foobar1'"} ] | |||
"check": [ {"condition": "json.getName() == 'account_reg_user@example.com'"} ] | |||
} | |||
}, | |||
@@ -60,12 +58,12 @@ | |||
}, | |||
{ | |||
"comment": "login as foobar1, works", | |||
"comment": "login as account_reg_user@example.com, works", | |||
"request": { | |||
"session": "new", | |||
"uri": "auth/login", | |||
"entity": { | |||
"name": "foobar1", | |||
"name": "account_reg_user@example.com", | |||
"password": "password1!" | |||
} | |||
}, | |||
@@ -80,7 +78,7 @@ | |||
"request": { "uri": "me" }, | |||
"response": { | |||
"store": "user1", | |||
"check": [ {"condition": "json.getName() == 'foobar1'"} ] | |||
"check": [ {"condition": "json.getName() == 'account_reg_user@example.com'"} ] | |||
} | |||
}, | |||
@@ -158,7 +156,7 @@ | |||
"session": "new", | |||
"uri": "auth/register", | |||
"entity": { | |||
"name": "foobar1", | |||
"name": "account_reg_user@example.com", | |||
"password": "password1!", | |||
"agreeToTerms": true | |||
} | |||
@@ -176,7 +174,7 @@ | |||
"session": "new", | |||
"uri": "auth/login", | |||
"entity": { | |||
"name": "foobar1", | |||
"name": "account_reg_user@example.com", | |||
"password": "password1!" | |||
} | |||
}, | |||
@@ -202,7 +200,7 @@ | |||
"session": "new", | |||
"uri": "auth/register", | |||
"entity": { | |||
"name": "foobar1", | |||
"name": "account_reg_user@example.com", | |||
"password": "password1!", | |||
"agreeToTerms": true | |||
} | |||
@@ -219,10 +217,9 @@ | |||
"session": "new", | |||
"uri": "auth/register", | |||
"entity": { | |||
"name": "foobar2", | |||
"name": "another_account_reg_user@example.com", | |||
"password": "password2!", | |||
"agreeToTerms": true, | |||
"contact": { "type": "email", "info": "user2-{{rand 5}}@example.com" } | |||
"agreeToTerms": true | |||
} | |||
}, | |||
"response": { | |||
@@ -235,7 +232,7 @@ | |||
"comment": "view new user", | |||
"request": { "uri": "me" }, | |||
"response": { | |||
"check": [ {"condition": "json.getName() == 'foobar2'"} ] | |||
"check": [ {"condition": "json.getName() == 'another_account_reg_user@example.com'"} ] | |||
} | |||
} | |||
] |
@@ -6,7 +6,7 @@ | |||
"method": "put", | |||
"uri": "auth/activate", | |||
"entity": { | |||
"name": "root2", | |||
"email": "root2", | |||
"password": "pass123!@x" | |||
} | |||
}, | |||
@@ -39,7 +39,7 @@ | |||
"comment": "read self", | |||
"request": { "uri": "me" }, | |||
"response": { | |||
"check": [ {"condition": "json.getName() == 'root'"} ] | |||
"check": [ {"condition": "json.getEmail() == 'root'"} ] | |||
} | |||
}, | |||
@@ -21,7 +21,6 @@ | |||
"comment": "as root user, create another admin user", | |||
"include": "new_account", | |||
"params": { | |||
"username": "admin-change_password", | |||
"email": "admin-change_password@example.com", | |||
"password": "bazquux1!", | |||
"admin": true, | |||
@@ -35,7 +34,7 @@ | |||
"session": "new", | |||
"uri": "auth/login", | |||
"entity": { | |||
"name": "admin-change_password", | |||
"name": "admin-change_password@example.com", | |||
"password": "bazquux1!" | |||
} | |||
}, | |||
@@ -50,7 +49,7 @@ | |||
"request": { "uri": "me" }, | |||
"response": { | |||
"check": [ | |||
{"condition": "json.getName() === 'admin-change_password'"}, | |||
{"condition": "json.getName() === 'admin-change_password@example.com'"}, | |||
{"condition": "json.admin() === true"} | |||
] | |||
} | |||
@@ -150,7 +149,7 @@ | |||
{ | |||
"comment": "as root user, try to change admin user password without sending current password, succeeds because we are senior admin", | |||
"request": { | |||
"uri": "users/admin-change_password/changePassword", | |||
"uri": "users/admin-change_password@example.com/changePassword", | |||
"entity": { | |||
"newPassword": "newadminPASS1!" | |||
} | |||
@@ -163,7 +162,7 @@ | |||
"session": "new", | |||
"uri": "auth/login", | |||
"entity": { | |||
"name": "admin-change_password", | |||
"name": "admin-change_password@example.com", | |||
"password": "bazquux1!" | |||
} | |||
}, | |||
@@ -178,7 +177,7 @@ | |||
"session": "new", | |||
"uri": "auth/login", | |||
"entity": { | |||
"name": "admin-change_password", | |||
"name": "admin-change_password@example.com", | |||
"password": "newadminPASS1!" | |||
} | |||
}, | |||
@@ -192,7 +191,7 @@ | |||
"comment": "as second admin, read self, succeeds", | |||
"request": { "uri": "me" }, | |||
"response": { | |||
"check": [ {"condition": "json.getName() === 'admin-change_password'"} ] | |||
"check": [ {"condition": "json.getName() === 'admin-change_password@example.com'"} ] | |||
} | |||
} | |||
@@ -140,9 +140,7 @@ | |||
"comment": "create a non-admin user", | |||
"include": "new_account", | |||
"params": { | |||
"username": "user-change_password", | |||
"email": "user-change_password@example.com", | |||
"password": "foobar1!", | |||
"verifyEmail": "true" | |||
} | |||
}, | |||
@@ -153,7 +151,7 @@ | |||
"session": "new", | |||
"uri": "auth/login", | |||
"entity": { | |||
"name": "user-change_password", | |||
"name": "user-change_password@example.com", | |||
"password": "foobar1!" | |||
} | |||
}, | |||
@@ -167,14 +165,14 @@ | |||
"comment": "as non-admin, read self-profile, succeeds", | |||
"request": { "uri": "me" }, | |||
"response": { | |||
"check": [ {"condition": "json.getName() === 'user-change_password'"} ] | |||
"check": [ {"condition": "json.getName() === 'user-change_password@example.com'"} ] | |||
} | |||
}, | |||
{ | |||
"comment": "as root, change non-admin user password, do not need to send old password, invalidates all sessions", | |||
"request": { | |||
"uri": "users/user-change_password/changePassword", | |||
"uri": "users/user-change_password@example.com/changePassword", | |||
"session": "rootSession", | |||
"entity": { | |||
"newPassword": "newuserpass1!" | |||
@@ -199,7 +197,7 @@ | |||
"session": "new", | |||
"uri": "auth/login", | |||
"entity": { | |||
"name": "user-change_password", | |||
"name": "user-change_password@example.com", | |||
"password": "foobar1!" | |||
} | |||
}, | |||
@@ -214,7 +212,7 @@ | |||
"session": "new", | |||
"uri": "auth/login", | |||
"entity": { | |||
"name": "user-change_password", | |||
"name": "user-change_password@example.com", | |||
"password": "newuserpass1!" | |||
} | |||
}, | |||
@@ -226,7 +224,7 @@ | |||
{ | |||
"comment": "as non-admin, read self policy, succeeds", | |||
"request": { "uri": "users/user-change_password/policy" }, | |||
"request": { "uri": "users/user-change_password@example.com/policy" }, | |||
"response": { "store": "userPolicy" } | |||
}, | |||
@@ -284,7 +282,7 @@ | |||
"uri": "auth/approve/{{emailInbox.[0].ctx.confirmationToken}}", | |||
"method": "post", | |||
"entity": [ | |||
{ "name": "account", "value": "user-change_password" }, | |||
{ "name": "account", "value": "user-change_password@example.com" }, | |||
{ "name": "password", "value": "new_password3!" } | |||
] | |||
} | |||
@@ -296,7 +294,7 @@ | |||
"session": "new", | |||
"uri": "auth/login", | |||
"entity": { | |||
"name": "user-change_password", | |||
"name": "user-change_password@example.com", | |||
"password": "new_password3!" | |||
} | |||
}, | |||
@@ -309,7 +307,7 @@ | |||
{ | |||
"comment": "as non-admin user, update profile, set email as not required for account operations", | |||
"request": { | |||
"uri": "users/user-change_password/policy/contacts", | |||
"uri": "users/user-change_password@example.com/policy/contacts", | |||
"entity": { | |||
"type": "email", | |||
"info": "user-change_password@example.com", | |||
@@ -343,7 +341,7 @@ | |||
"session": "new", | |||
"uri": "auth/login", | |||
"entity": { | |||
"name": "user-change_password", | |||
"name": "user-change_password@example.com", | |||
"password": "new_password4!" | |||
} | |||
}, | |||
@@ -357,14 +355,14 @@ | |||
"comment": "update user policy, add authenticator", | |||
"include": "add_authenticator", | |||
"params": { | |||
"userId": "user-change_password", | |||
"userId": "user-change_password@example.com", | |||
"authenticatorVar": "userAuthenticator" | |||
} | |||
}, | |||
{ | |||
"comment": "re-read user policy, verify authenticator is required for account operations", | |||
"request": { "uri": "users/user-change_password/policy" }, | |||
"request": { "uri": "users/user-change_password@example.com/policy" }, | |||
"response": { | |||
"store": "userPolicy", | |||
"check": [ | |||
@@ -1,11 +1,7 @@ | |||
[ | |||
{ | |||
"comment": "create an account", | |||
"include": "new_account", | |||
"params": { | |||
"name": "test-user-{{rand 5}}", | |||
"password": "password1!" | |||
} | |||
"include": "new_account" | |||
}, | |||
{ | |||
@@ -3,9 +3,7 @@ | |||
"comment": "activate service, create account, login", | |||
"include": "new_account", | |||
"params": { | |||
"username": "testuser", | |||
"password": "foobar1!", | |||
"email": "testuser@example.com", | |||
"email": "download_account_user@example.com", | |||
"verifyEmail": "true", | |||
"userSessionName": "userSession" | |||
} | |||
@@ -33,7 +31,7 @@ | |||
"comment": "as root, check email inbox for download request message", | |||
"request": { | |||
"session": "rootSession", | |||
"uri": "debug/inbox/email/testuser@example.com?type=request&action=download" | |||
"uri": "debug/inbox/email/download_account_user@example.com?type=request&action=download" | |||
}, | |||
"response": { | |||
"store": "emailInbox", | |||
@@ -59,7 +57,7 @@ | |||
"comment": "as root, check email inbox for download confirmation", | |||
"request": { | |||
"session": "rootSession", | |||
"uri": "debug/inbox/email/testuser@example.com?type=confirmation&action=download" | |||
"uri": "debug/inbox/email/download_account_user@example.com?type=confirmation&action=download" | |||
}, | |||
"response": { | |||
"store": "emailInbox", | |||
@@ -1,11 +1,9 @@ | |||
[ | |||
{ | |||
"comment": "activate service, create account, login", | |||
"comment": "create account, login", | |||
"include": "new_account", | |||
"params": { | |||
"username": "user-forgot_password", | |||
"email": "user-forgot_password@example.com", | |||
"password": "foobar1!", | |||
"verifyEmail": "true" | |||
} | |||
}, | |||
@@ -62,7 +60,7 @@ | |||
"request": { | |||
"session": "userSession", | |||
"uri": "auth/approve/{{smsInbox.[0].ctx.confirmationToken}}", | |||
"entity": [{"name": "account", "value": "user-forgot_password"}] | |||
"entity": [{"name": "account", "value": "user-forgot_password@example.com"}] | |||
} | |||
}, | |||
@@ -120,7 +118,7 @@ | |||
"session": "new", | |||
"uri": "auth/approve/{{emailInbox.[0].ctx.confirmationToken}}", | |||
"entity": [ | |||
{ "name": "account", "value": "user-forgot_password" }, | |||
{ "name": "account", "value": "user-forgot_password@example.com" }, | |||
{ "name": "password", "value": "new_password" } | |||
] | |||
}, | |||
@@ -136,7 +134,7 @@ | |||
"session": "new", | |||
"uri": "auth/approve/{{emailInbox.[0].ctx.confirmationToken}}", | |||
"entity": [ | |||
{ "name": "account", "value": "user-forgot_password" }, | |||
{ "name": "account", "value": "user-forgot_password@example.com" }, | |||
{ "name": "password", "value": "try_new_password1!" } | |||
] | |||
}, | |||
@@ -155,7 +153,7 @@ | |||
"session": "new", | |||
"uri": "auth/approve/{{smsInbox.[0].ctx.confirmationToken}}", | |||
"entity": [ | |||
{ "name": "account", "value": "user-forgot_password" }, | |||
{ "name": "account", "value": "user-forgot_password@example.com" }, | |||
{ "name": "password", "value": "new_password1!" } | |||
] | |||
} | |||
@@ -167,7 +165,7 @@ | |||
"session": "new", | |||
"uri": "auth/approve/{{smsInbox.[0].ctx.confirmationToken}}", | |||
"entity": [ | |||
{ "name": "account", "value": "user-forgot_password" }, | |||
{ "name": "account", "value": "user-forgot_password@example.com" }, | |||
{ "name": "password", "value": "new_password2!" } | |||
] | |||
}, | |||
@@ -3,9 +3,7 @@ | |||
"comment": "create new account and login", | |||
"include": "new_account", | |||
"params": { | |||
"username": "user-multifactor_auth", | |||
"email": "user-multifactor_auth@example.com", | |||
"password": "foobar1!" | |||
"email": "user-multifactor_auth@example.com" | |||
} | |||
}, | |||
@@ -74,7 +72,7 @@ | |||
"request": { | |||
"session": "userSession", | |||
"uri": "auth/approve/{{userInbox.[0].ctx.confirmationToken}}", | |||
"entity": [{"name": "account", "value": "user-multifactor_auth"}] | |||
"entity": [{"name": "account", "value": "user-multifactor_auth@example.com"}] | |||
} | |||
}, | |||
@@ -172,7 +170,7 @@ | |||
"request": { | |||
"session": "userSession", | |||
"uri": "auth/approve/{{userInbox.[0].ctx.confirmationToken}}", | |||
"entity": [{"name": "account", "value": "user-multifactor_auth"}] | |||
"entity": [{"name": "account", "value": "user-multifactor_auth@example.com"}] | |||
}, | |||
"response": { | |||
"sessionName": "userSession", | |||
@@ -250,7 +248,7 @@ | |||
"request": { | |||
"session": "userSession", | |||
"uri": "auth/approve/{{userInbox.[0].ctx.confirmationToken}}", | |||
"entity": [{"name": "account", "value": "user-multifactor_auth"}] | |||
"entity": [{"name": "account", "value": "user-multifactor_auth@example.com"}] | |||
}, | |||
"response": { | |||
"status": 422, | |||
@@ -264,7 +262,7 @@ | |||
"session": "userSession", | |||
"uri": "auth/approve/{{userInbox.[0].ctx.confirmationToken}}", | |||
"entity": [ | |||
{"name": "account", "value": "user-multifactor_auth"}, | |||
{"name": "account", "value": "user-multifactor_auth@example.com"}, | |||
{"name": "totpToken", "value": "{{authenticator_token authenticator.totpKey}}"} | |||
] | |||
}, | |||
@@ -374,7 +372,7 @@ | |||
"request": { | |||
"session": "userSession", | |||
"uri": "auth/approve/{{smsInbox.[0].ctx.confirmationToken}}", | |||
"entity": [{"name": "account", "value": "user-multifactor_auth"}] | |||
"entity": [{"name": "account", "value": "user-multifactor_auth@example.com"}] | |||
} | |||
}, | |||
@@ -461,7 +459,7 @@ | |||
"request": { | |||
"session": "userSession", | |||
"uri": "auth/approve/{{smsInbox.[0].ctx.confirmationToken}}", | |||
"entity": [{"name": "account", "value": "user-multifactor_auth"}] | |||
"entity": [{"name": "account", "value": "user-multifactor_auth@example.com"}] | |||
}, | |||
"response": { | |||
"status": 422, | |||
@@ -475,7 +473,7 @@ | |||
"session": "userSession", | |||
"uri": "auth/approve/{{smsInbox.[0].ctx.confirmationToken}}", | |||
"entity": [ | |||
{"name": "account", "value": "user-multifactor_auth"}, | |||
{"name": "account", "value": "user-multifactor_auth@example.com"}, | |||
{"name": "totpToken", "value": "{{authenticator_token authenticator.totpKey}}"} | |||
] | |||
}, | |||
@@ -552,7 +550,7 @@ | |||
"request": { | |||
"session": "userSession", | |||
"uri": "auth/approve/{{userInbox.[0].ctx.confirmationToken}}", | |||
"entity": [{"name": "account", "value": "user-multifactor_auth"}] | |||
"entity": [{"name": "account", "value": "user-multifactor_auth@example.com"}] | |||
} | |||
}, | |||
@@ -621,7 +619,7 @@ | |||
"request": { | |||
"session": "userSession", | |||
"uri": "auth/approve/{{userInbox.[0].ctx.confirmationToken}}", | |||
"entity": [{"name": "account", "value": "user-multifactor_auth"}] | |||
"entity": [{"name": "account", "value": "user-multifactor_auth@example.com"}] | |||
}, | |||
"response": { | |||
"sessionName": "userSession", | |||
@@ -89,10 +89,9 @@ | |||
"uri": "users", | |||
"method": "put", | |||
"entity": { | |||
"name": "user1", | |||
"name": "backup_and_restore_user@example.com", | |||
"password": "password1!", | |||
"agreeToTerms": true, | |||
"contact": {"type": "email", "info": "user-{{rand 5}}@example.com"} | |||
"agreeToTerms": true | |||
} | |||
} | |||
}, | |||
@@ -100,7 +99,7 @@ | |||
{ | |||
"before": "sleep 24s", | |||
"comment": "lookup policy for new account", | |||
"request": { "uri": "users/user1/policy" }, | |||
"request": { "uri": "users/backup_and_restore_user/policy" }, | |||
"response": { "store": "user1policy" } | |||
}, | |||
@@ -195,7 +194,7 @@ | |||
{ | |||
"comment": "verify account we added has been restored", | |||
"request": { | |||
"uri": "users/user1/policy" | |||
"uri": "users/backup_and_restore_user/policy" | |||
}, | |||
"response": { | |||
"check": [ {"condition": "json.getFirstEmail() == user1policy.getFirstEmail()"} ] | |||
@@ -13,10 +13,9 @@ | |||
"uri": "users", | |||
"method": "put", | |||
"entity": { | |||
"name": "test_user_simple_net", | |||
"name": "test_user_simple_net@example.com", | |||
"password": "password1!", | |||
"agreeToTerms": true, | |||
"contact": {"type": "email", "info": "test-user@example.com"} | |||
"agreeToTerms": true | |||
} | |||
}, | |||
"response": { | |||
@@ -341,10 +340,9 @@ | |||
"uri": "users", | |||
"method": "put", | |||
"entity": { | |||
"name": "bubble_user", | |||
"name": "bubble_user@example.com", | |||
"password": "password1!", | |||
"agreeToTerms": true, | |||
"contact": {"type": "email", "info": "bubble-user@example.com"} | |||
"agreeToTerms": true | |||
} | |||
} | |||
}, | |||
@@ -5,10 +5,9 @@ | |||
"uri": "users", | |||
"method": "put", | |||
"entity": { | |||
"name": "test_user_code", | |||
"name": "test_user_code@example.com", | |||
"password": "password1!", | |||
"agreeToTerms": true, | |||
"contact": {"type": "email", "info": "test-user@example.com"} | |||
"agreeToTerms": true | |||
} | |||
} | |||
}, | |||
@@ -20,7 +19,7 @@ | |||
"session": "new", | |||
"uri": "auth/login", | |||
"entity": { | |||
"name": "test_user_code", | |||
"name": "test_user_code@example.com", | |||
"password": "password1!" | |||
} | |||
}, | |||
@@ -5,10 +5,9 @@ | |||
"uri": "users", | |||
"method": "put", | |||
"entity": { | |||
"name": "test_user_credit", | |||
"name": "test_user_credit@example.com", | |||
"password": "password1!", | |||
"agreeToTerms": true, | |||
"contact": {"type": "email", "info": "test-user@example.com"} | |||
"agreeToTerms": true | |||
} | |||
} | |||
}, | |||
@@ -20,7 +19,7 @@ | |||
"session": "new", | |||
"uri": "auth/login", | |||
"entity": { | |||
"name": "test_user_credit", | |||
"name": "test_user_credit@example.com", | |||
"password": "password1!" | |||
} | |||
}, | |||
@@ -106,7 +105,7 @@ | |||
"comment": "as root, check email inbox for verification message", | |||
"request": { | |||
"session": "rootSession", | |||
"uri": "debug/inbox/email/test-user@example.com?type=request&action=verify&target=account" | |||
"uri": "debug/inbox/email/test_user_credit@example.com?type=request&action=verify&target=account" | |||
}, | |||
"response": { | |||
"store": "emailInbox", | |||
@@ -5,10 +5,9 @@ | |||
"uri": "users", | |||
"method": "put", | |||
"entity": { | |||
"name": "test_user_credit_refund_restart", | |||
"name": "test_user_credit_refund_restart@example.com", | |||
"password": "password1!", | |||
"agreeToTerms": true, | |||
"contact": {"type": "email", "info": "test-user@example.com"} | |||
"agreeToTerms": true | |||
} | |||
} | |||
}, | |||
@@ -20,7 +19,7 @@ | |||
"session": "new", | |||
"uri": "auth/login", | |||
"entity": { | |||
"name": "test_user_credit_refund_restart", | |||
"name": "test_user_credit_refund_restart@example.com", | |||
"password": "password1!" | |||
} | |||
}, | |||
@@ -45,7 +44,7 @@ | |||
"comment": "as root, check email inbox for verification message", | |||
"request": { | |||
"session": "rootSession", | |||
"uri": "debug/inbox/email/test-user@example.com?type=request&action=verify&target=account" | |||
"uri": "debug/inbox/email/test_user_credit_refund_restart@example.com?type=request&action=verify&target=account" | |||
}, | |||
"response": { | |||
"store": "emailInbox", | |||
@@ -198,7 +197,7 @@ | |||
{"condition": "json[0].getAmount() < {{bills.[0].total}}"}, | |||
// not sure if current month has 28, 29, 30 or 31 days, so let's make some reasonable bounds | |||
// we should only have a refund for all but one day | |||
{"condition": "json[0].getAmount() > Math.round(bills[0].getTotal() - (bills[0].getTotal() / (bills[0].daysInPeriod() - 1)))"}, | |||
{"condition": "json[0].getAmount() >= Math.round((bills[0].daysInPeriod() - 1) * (bills[0].getTotal() / bills[0].daysInPeriod()))"}, | |||
{"condition": "json[0].getAmount() <= Math.round(bills[0].getTotal() - (bills[0].getTotal() / bills[0].daysInPeriod()))"} | |||
] | |||
} | |||
@@ -5,10 +5,9 @@ | |||
"uri": "users", | |||
"method": "put", | |||
"entity": { | |||
"name": "test_user_free", | |||
"name": "test_user_free@example.com", | |||
"password": "password1!", | |||
"agreeToTerms": true, | |||
"contact": {"type": "email", "info": "test-user@example.com"} | |||
"agreeToTerms": true | |||
} | |||
} | |||
}, | |||
@@ -20,7 +19,7 @@ | |||
"session": "new", | |||
"uri": "auth/login", | |||
"entity": { | |||
"name": "test_user_free", | |||
"name": "test_user_free@example.com", | |||
"password": "password1!" | |||
} | |||
}, | |||
@@ -5,10 +5,9 @@ | |||
"uri": "users", | |||
"method": "put", | |||
"entity": { | |||
"name": "test_plan_apps_user", | |||
"name": "test_plan_apps_user@example.com", | |||
"password": "password1!", | |||
"agreeToTerms": true, | |||
"contact": {"type": "email", "info": "test-user@example.com"} | |||
"agreeToTerms": true | |||
} | |||
} | |||
}, | |||
@@ -20,7 +19,7 @@ | |||
"session": "new", | |||
"uri": "auth/login", | |||
"entity": { | |||
"name": "test_plan_apps_user", | |||
"name": "test_plan_apps_user@example.com", | |||
"password": "password1!" | |||
} | |||
}, | |||
@@ -5,10 +5,9 @@ | |||
"uri": "users", | |||
"method": "put", | |||
"entity": { | |||
"name": "test_user_recurring", | |||
"name": "test_user_recurring@example.com", | |||
"password": "password1!", | |||
"agreeToTerms": true, | |||
"contact": {"type": "email", "info": "test-user@example.com"} | |||
"agreeToTerms": true | |||
} | |||
} | |||
}, | |||
@@ -20,7 +19,7 @@ | |||
"session": "new", | |||
"uri": "auth/login", | |||
"entity": { | |||
"name": "test_user_recurring", | |||
"name": "test_user_recurring@example.com", | |||
"password": "password1!" | |||
} | |||
}, | |||
@@ -45,7 +44,7 @@ | |||
"comment": "as root, check email inbox for verification message", | |||
"request": { | |||
"session": "rootSession", | |||
"uri": "debug/inbox/email/test-user@example.com?type=request&action=verify&target=account" | |||
"uri": "debug/inbox/email/test_user_recurring@example.com?type=request&action=verify&target=account" | |||
}, | |||
"response": { | |||
"store": "emailInbox", | |||
@@ -346,7 +345,7 @@ | |||
"comment": "as root, verify payment reminder message has been sent", | |||
"request": { | |||
"session": "rootSession", | |||
"uri": "debug/inbox/email/test-user@example.com?type=request&action=payment&target=network" | |||
"uri": "debug/inbox/email/test_user_recurring@example.com?type=request&action=payment&target=network" | |||
}, | |||
"response": { | |||
"store": "emailInbox", | |||
@@ -380,7 +379,7 @@ | |||
"comment": "as root, verify nonpayment message has been sent", | |||
"request": { | |||
"session": "rootSession", | |||
"uri": "debug/inbox/email/test-user@example.com?type=notice&action=payment&target=network" | |||
"uri": "debug/inbox/email/test_user_recurring@example.com?type=notice&action=payment&target=network" | |||
}, | |||
"response": { | |||
"store": "emailInbox", | |||
@@ -5,10 +5,9 @@ | |||
"session": "new", | |||
"uri": "auth/register", | |||
"entity": { | |||
"name": "test_user_small_promo", | |||
"name": "account_credit_user@example.com", | |||
"password": "password1!", | |||
"agreeToTerms": true, | |||
"contact": {"type": "email", "info": "test_user_small_promo@example.com"} | |||
"agreeToTerms": true | |||
} | |||
}, | |||
"response": { | |||
@@ -23,7 +22,7 @@ | |||
"comment": "root: check email inbox for verification message", | |||
"request": { | |||
"session": "rootSession", | |||
"uri": "debug/inbox/email/test_user_small_promo@example.com?type=request&action=verify&target=account" | |||
"uri": "debug/inbox/email/account_credit_user@example.com?type=request&action=verify&target=account" | |||
}, | |||
"response": { | |||
"store": "emailInbox", | |||
@@ -83,7 +82,7 @@ | |||
{ | |||
"comment": "root: apply AccountCredit5 promotion to account", | |||
"request": { | |||
"uri": "users/test_user_small_promo/promos", | |||
"uri": "users/account_credit_user@example.com/promos", | |||
"method": "put", | |||
"entity": { | |||
"name": "AccountCredit5" | |||
@@ -93,7 +92,7 @@ | |||
{ | |||
"comment": "root: list promos available, should be credit", | |||
"request": {"uri": "users/test_user_small_promo/promos"}, | |||
"request": {"uri": "users/account_credit_user@example.com/promos"}, | |||
"response": { | |||
"check": [ | |||
{"condition": "json.length === 1"}, | |||
@@ -119,7 +118,7 @@ | |||
{ | |||
"comment": "user: try to add second AccountCredit5, admin only", | |||
"request": { | |||
"uri": "users/test_user_small_promo/promos", | |||
"uri": "users/account_credit_user@example.com/promos", | |||
"method": "put", | |||
"entity": { | |||
"name": "AccountCredit5" | |||
@@ -132,7 +131,7 @@ | |||
"comment": "root: add second AccountCredit5 promotion to account", | |||
"request": { | |||
"session": "rootSession", | |||
"uri": "users/test_user_small_promo/promos", | |||
"uri": "users/account_credit_user@example.com/promos", | |||
"method": "put", | |||
"entity": { | |||
"name": "AccountCredit5" | |||
@@ -143,7 +142,7 @@ | |||
{ | |||
"comment": "root: add third AccountCredit5 promotion to account", | |||
"request": { | |||
"uri": "users/test_user_small_promo/promos", | |||
"uri": "users/account_credit_user@example.com/promos", | |||
"method": "put", | |||
"entity": { | |||
"name": "AccountCredit5" | |||
@@ -154,7 +153,7 @@ | |||
{ | |||
"comment": "root: add fourth AccountCredit5 promotion to account", | |||
"request": { | |||
"uri": "users/test_user_small_promo/promos", | |||
"uri": "users/account_credit_user@example.com/promos", | |||
"method": "put", | |||
"entity": { | |||
"name": "AccountCredit5" | |||
@@ -167,7 +166,7 @@ | |||
{ | |||
"comment": "root: list promos available, should be four credits", | |||
"request": {"uri": "users/test_user_small_promo/promos"}, | |||
"request": {"uri": "users/account_credit_user@example.com/promos"}, | |||
"response": { | |||
"check": [ | |||
{"condition": "json.length === 4"}, | |||
@@ -200,7 +199,7 @@ | |||
"comment": "root: remove last AccountCredit5 promotion", | |||
"request": { | |||
"session": "rootSession", | |||
"uri": "users/test_user_small_promo/promos/{{promos.[0].uuid}}", | |||
"uri": "users/account_credit_user@example.com/promos/{{promos.[0].uuid}}", | |||
"method": "delete" | |||
}, | |||
"response": { | |||
@@ -475,7 +474,7 @@ | |||
"comment": "root: apply another AccountCredit5 promotion to account", | |||
"request": { | |||
"session": "rootSession", | |||
"uri": "users/test_user_small_promo/promos", | |||
"uri": "users/account_credit_user@example.com/promos", | |||
"method": "put", | |||
"entity": { | |||
"name": "AccountCredit5" | |||
@@ -5,10 +5,9 @@ | |||
"session": "new", | |||
"uri": "auth/register", | |||
"entity": { | |||
"name": "test_user_1mo_free", | |||
"name": "test_user_1mo_free@example.com", | |||
"password": "password1!", | |||
"agreeToTerms": true, | |||
"contact": {"type": "email", "info": "test_user_1mo_free@example.com"} | |||
"agreeToTerms": true | |||
} | |||
}, | |||
"response": { | |||
@@ -5,10 +5,9 @@ | |||
"session": "new", | |||
"uri": "auth/register", | |||
"entity": { | |||
"name": "test_user_referring_multi", | |||
"name": "test_user_referring_multi@example.com", | |||
"password": "password1!", | |||
"agreeToTerms": true, | |||
"contact": {"type": "email", "info": "test_user_referring_multi@example.com"} | |||
"agreeToTerms": true | |||
} | |||
}, | |||
"response": { | |||
@@ -66,7 +65,7 @@ | |||
{ | |||
"comment": "as root, grant some referral codes to the referring user", | |||
"request": { | |||
"uri": "users/test_user_referring_multi/referralCodes", | |||
"uri": "users/test_user_referring_multi@example.com/referralCodes", | |||
"method": "put", | |||
"entity": { "count": 3 } | |||
}, | |||
@@ -484,7 +483,7 @@ | |||
"comment": "root: apply another AccountCredit5 promotion to referring account", | |||
"request": { | |||
"session": "rootSession", | |||
"uri": "users/test_user_referring_multi/promos", | |||
"uri": "users/test_user_referring_multi@example.com/promos", | |||
"method": "put", | |||
"entity": { | |||
"name": "AccountCredit5" | |||
@@ -5,10 +5,9 @@ | |||
"uri": "users", | |||
"method": "put", | |||
"entity": { | |||
"name": "test_user_referring_free", | |||
"name": "test_user_referring_free@example.com", | |||
"password": "password1!", | |||
"agreeToTerms": true, | |||
"contact": {"type": "email", "info": "test_user_referring_free@example.com"} | |||
"agreeToTerms": true | |||
} | |||
} | |||
}, | |||
@@ -20,7 +19,7 @@ | |||
"session": "new", | |||
"uri": "auth/login", | |||
"entity": { | |||
"name": "test_user_referring_free", | |||
"name": "test_user_referring_free@example.com", | |||
"password": "password1!" | |||
} | |||
}, | |||
@@ -61,7 +60,7 @@ | |||
{ | |||
"comment": "as root, grant some referral codes to the referring user", | |||
"request": { | |||
"uri": "users/test_user_referring_free/referralCodes", | |||
"uri": "users/test_user_referring_free@example.com/referralCodes", | |||
"method": "put", | |||
"entity": { "count": 3 } | |||
}, | |||
@@ -102,10 +101,9 @@ | |||
"session": "new", | |||
"uri": "auth/register", | |||
"entity": { | |||
"name": "test_user_referred_free", | |||
"name": "test_user_referred_free@example.com", | |||
"password": "password1!", | |||
"agreeToTerms": true, | |||
"contact": {"type": "email", "info": "test_user_referred_free@example.com"}, | |||
"promoCode": "{{referralCodes.[0].name}}" | |||
} | |||
}, | |||
@@ -1 +1 @@ | |||
Subproject commit 9d927b5184541a18798c58bf6683ee2d16faba87 | |||
Subproject commit e76602ecda245951a9b4fcd767a606c7013497c5 |
@@ -1 +1 @@ | |||
Subproject commit a07578cf1fde1cdaee0abc626fa4761fe8421446 | |||
Subproject commit 3b1649f05991edaf82d117ecf3080e46a16b63b4 |