mirror of https://github.com/laurent22/joplin.git
Server: Allow creating a user with a specific account type from admin UI
parent
3c181906c2
commit
ecd1602658
|
@ -38,6 +38,7 @@ export interface EnvVariables {
|
|||
|
||||
SIGNUP_ENABLED?: string;
|
||||
TERMS_ENABLED?: string;
|
||||
ACCOUNT_TYPES_ENABLED?: string;
|
||||
|
||||
ERROR_STACK_TRACES?: string;
|
||||
}
|
||||
|
@ -152,6 +153,7 @@ export async function initConfig(envType: Env, env: EnvVariables, overrides: any
|
|||
userContentBaseUrl: env.USER_CONTENT_BASE_URL ? env.USER_CONTENT_BASE_URL : baseUrl,
|
||||
signupEnabled: env.SIGNUP_ENABLED === '1',
|
||||
termsEnabled: env.TERMS_ENABLED === '1',
|
||||
accountTypesEnabled: env.ACCOUNT_TYPES_ENABLED === '1',
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@ import { formatBytes, MB } from '../utils/bytes';
|
|||
|
||||
export enum AccountType {
|
||||
Default = 0,
|
||||
Free = 1,
|
||||
Basic = 1,
|
||||
Pro = 2,
|
||||
}
|
||||
|
||||
|
@ -18,6 +18,11 @@ interface AccountTypeProperties {
|
|||
max_item_size: number;
|
||||
}
|
||||
|
||||
interface AccountTypeSelectOptions {
|
||||
value: number;
|
||||
label: string;
|
||||
}
|
||||
|
||||
export function accountTypeProperties(accountType: AccountType): AccountTypeProperties {
|
||||
const types: AccountTypeProperties[] = [
|
||||
{
|
||||
|
@ -26,7 +31,7 @@ export function accountTypeProperties(accountType: AccountType): AccountTypeProp
|
|||
max_item_size: 0,
|
||||
},
|
||||
{
|
||||
account_type: AccountType.Free,
|
||||
account_type: AccountType.Basic,
|
||||
can_share: 0,
|
||||
max_item_size: 10 * MB,
|
||||
},
|
||||
|
@ -37,7 +42,26 @@ export function accountTypeProperties(accountType: AccountType): AccountTypeProp
|
|||
},
|
||||
];
|
||||
|
||||
return types.find(a => a.account_type === accountType);
|
||||
const type = types.find(a => a.account_type === accountType);
|
||||
if (!type) throw new Error(`Invalid account type: ${accountType}`);
|
||||
return type;
|
||||
}
|
||||
|
||||
export function accountTypeOptions(): AccountTypeSelectOptions[] {
|
||||
return [
|
||||
{
|
||||
value: AccountType.Default,
|
||||
label: 'Default',
|
||||
},
|
||||
{
|
||||
value: AccountType.Basic,
|
||||
label: 'Basic',
|
||||
},
|
||||
{
|
||||
value: AccountType.Pro,
|
||||
label: 'Pro',
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
export default class UserModel extends BaseModel<User> {
|
||||
|
@ -68,6 +92,7 @@ export default class UserModel extends BaseModel<User> {
|
|||
if ('full_name' in object) user.full_name = object.full_name;
|
||||
if ('max_item_size' in object) user.max_item_size = object.max_item_size;
|
||||
if ('can_share' in object) user.can_share = object.can_share;
|
||||
if ('account_type' in object) user.account_type = object.account_type;
|
||||
|
||||
return user;
|
||||
}
|
||||
|
@ -96,6 +121,7 @@ export default class UserModel extends BaseModel<User> {
|
|||
if (user.is_admin && user.id === resource.id && 'is_admin' in resource && !resource.is_admin) throw new ErrorForbidden('admin user cannot make themselves a non-admin');
|
||||
if ('max_item_size' in resource && !user.is_admin && resource.max_item_size !== previousResource.max_item_size) throw new ErrorForbidden('non-admin user cannot change max_item_size');
|
||||
if ('can_share' in resource && !user.is_admin && resource.can_share !== previousResource.can_share) throw new ErrorForbidden('non-admin user cannot change can_share');
|
||||
if ('account_type' in resource && !user.is_admin && resource.account_type !== previousResource.account_type) throw new ErrorForbidden('non-admin user cannot change account_type');
|
||||
}
|
||||
|
||||
if (action === AclAction.Delete) {
|
||||
|
@ -197,6 +223,17 @@ export default class UserModel extends BaseModel<User> {
|
|||
await this.save({ id: user.id, email_confirmed: 1 });
|
||||
}
|
||||
|
||||
// public async saveWithAccountType(accountType:AccountType, user: User, options: SaveOptions = {}): Promise<User> {
|
||||
// if (accountType !== AccountType.Default) {
|
||||
// user = {
|
||||
// ...user,
|
||||
// ...accountTypeProperties(accountType),
|
||||
// };
|
||||
// }
|
||||
|
||||
// return this.save(user, options);
|
||||
// }
|
||||
|
||||
// Note that when the "password" property is provided, it is going to be
|
||||
// hashed automatically. It means that it is not safe to do:
|
||||
//
|
||||
|
|
|
@ -41,7 +41,7 @@ describe('index_signup', function() {
|
|||
// Check that the user has been created
|
||||
const user = await models().user().loadByEmail('toto@example.com');
|
||||
expect(user).toBeTruthy();
|
||||
expect(user.account_type).toBe(AccountType.Free);
|
||||
expect(user.account_type).toBe(AccountType.Basic);
|
||||
expect(user.email_confirmed).toBe(0);
|
||||
expect(user.can_share).toBe(0);
|
||||
expect(user.max_item_size).toBe(10 * MB);
|
||||
|
|
|
@ -44,7 +44,7 @@ router.post('signup', async (_path: SubPath, ctx: AppContext) => {
|
|||
const password = checkPassword(formUser, true);
|
||||
|
||||
const user = await ctx.models.user().save({
|
||||
...accountTypeProperties(AccountType.Free),
|
||||
...accountTypeProperties(AccountType.Basic),
|
||||
email: formUser.email,
|
||||
full_name: formUser.full_name,
|
||||
password,
|
||||
|
|
|
@ -11,6 +11,7 @@ import defaultView from '../../utils/defaultView';
|
|||
import { AclAction } from '../../models/BaseModel';
|
||||
import { NotificationKey } from '../../models/NotificationModel';
|
||||
import { formatBytes } from '../../utils/bytes';
|
||||
import { accountTypeOptions, accountTypeProperties } from '../../models/UserModel';
|
||||
|
||||
interface CheckPasswordInput {
|
||||
password: string;
|
||||
|
@ -29,7 +30,7 @@ export function checkPassword(fields: CheckPasswordInput, required: boolean): st
|
|||
}
|
||||
|
||||
function makeUser(isNew: boolean, fields: any): User {
|
||||
const user: User = {};
|
||||
let user: User = {};
|
||||
|
||||
if ('email' in fields) user.email = fields.email;
|
||||
if ('full_name' in fields) user.full_name = fields.full_name;
|
||||
|
@ -37,6 +38,14 @@ function makeUser(isNew: boolean, fields: any): User {
|
|||
if ('max_item_size' in fields) user.max_item_size = fields.max_item_size || 0;
|
||||
if ('can_share' in fields) user.can_share = fields.can_share ? 1 : 0;
|
||||
|
||||
if ('account_type' in fields) {
|
||||
user.account_type = Number(fields.account_type);
|
||||
user = {
|
||||
...user,
|
||||
...accountTypeProperties(user.account_type),
|
||||
};
|
||||
}
|
||||
|
||||
const password = checkPassword(fields, false);
|
||||
if (password) user.password = password;
|
||||
|
||||
|
@ -108,6 +117,14 @@ router.get('users/:id', async (path: SubPath, ctx: AppContext, user: User = null
|
|||
view.content.postUrl = postUrl;
|
||||
view.content.showDeleteButton = !isNew && !!owner.is_admin && owner.id !== user.id;
|
||||
|
||||
if (config().accountTypesEnabled) {
|
||||
view.content.showAccountTypes = true;
|
||||
view.content.accountTypes = accountTypeOptions().map((o: any) => {
|
||||
o.selected = user.account_type === o.value;
|
||||
return o;
|
||||
});
|
||||
}
|
||||
|
||||
return view;
|
||||
});
|
||||
|
||||
|
|
|
@ -80,6 +80,7 @@ export interface Config {
|
|||
userContentBaseUrl: string;
|
||||
signupEnabled: boolean;
|
||||
termsEnabled: boolean;
|
||||
accountTypesEnabled: boolean;
|
||||
showErrorStackTraces: boolean;
|
||||
database: DatabaseConfig;
|
||||
mailer: MailerConfig;
|
||||
|
|
|
@ -15,6 +15,19 @@
|
|||
</div>
|
||||
</div>
|
||||
{{#global.owner.is_admin}}
|
||||
{{#showAccountTypes}}
|
||||
<div class="field">
|
||||
<label class="label">Account type</label>
|
||||
<div class="select">
|
||||
<select name="account_type">
|
||||
{{#accountTypes}}
|
||||
<option value="{{value}}" {{#selected}}selected{{/selected}}>{{label}}</option>
|
||||
{{/accountTypes}}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
{{/showAccountTypes}}
|
||||
|
||||
<div class="field">
|
||||
<label class="label">Max item size</label>
|
||||
<div class="control">
|
||||
|
|
Loading…
Reference in New Issue