Add user flags field

This commit is contained in:
Jonas Lochmann
2020-06-01 02:00:00 +02:00
parent 7b6527752e
commit 12dc22f8a2
26 changed files with 661 additions and 54 deletions
+1
View File
@@ -66,3 +66,4 @@ export { UpdateNetworkTimeVerificationAction } from './updatenetworktimeverifica
export { UpdateParentBlockedTimesAction } from './updateparentblockedtimes'
export { UpdateParentNotificationFlagsAction } from './updateparentnotificationflags'
export { UpdateTimelimitRuleAction } from './updatetimelimitrule'
export { UpdateUserFlagsAction } from './updateuserflags'
+5 -1
View File
@@ -55,6 +55,7 @@ import { SerialiizedUpdateNetworkTimeVerificationAction, UpdateNetworkTimeVerifi
import { SerializedUpdateParentBlockedTimesAction, UpdateParentBlockedTimesAction } from '../updateparentblockedtimes'
import { SerializedUpdateParentNotificationFlagsAction, UpdateParentNotificationFlagsAction } from '../updateparentnotificationflags'
import { SerializedUpdateTimelimitRuleAction, UpdateTimelimitRuleAction } from '../updatetimelimitrule'
import { SerializedUpdateUserFlagsAction, UpdateUserFlagsAction } from '../updateuserflags'
export type SerializedParentAction =
SerializedAddCategoryAppsAction |
@@ -95,7 +96,8 @@ export type SerializedParentAction =
SerialiizedUpdateNetworkTimeVerificationAction |
SerializedUpdateParentBlockedTimesAction |
SerializedUpdateParentNotificationFlagsAction |
SerializedUpdateTimelimitRuleAction
SerializedUpdateTimelimitRuleAction |
SerializedUpdateUserFlagsAction
export const parseParentAction = (action: SerializedParentAction): ParentAction => {
if (action.type === 'ADD_CATEGORY_APPS') {
@@ -176,6 +178,8 @@ export const parseParentAction = (action: SerializedParentAction): ParentAction
return UpdateParentNotificationFlagsAction.parse(action)
} else if (action.type === 'UPDATE_TIMELIMIT_RULE') {
return UpdateTimelimitRuleAction.parse(action)
} else if (action.type === 'UPDATE_USER_FLAGS') {
return UpdateUserFlagsAction.parse(action)
} else {
throw new Error('illegal state: invalid type for action at parseParentAction')
}
+70
View File
@@ -0,0 +1,70 @@
/*
* server component for the TimeLimit App
* Copyright (C) 2019 - 2020 Jonas Lochmann
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { UserFlags } from '../model/userflags'
import { assertIdWithinFamily } from '../util/token'
import { ParentAction } from './basetypes'
export class UpdateUserFlagsAction extends ParentAction {
readonly userId: string
readonly modifiedBits: number
readonly newValues: number
constructor ({ userId, modifiedBits, newValues }: {
userId: string
modifiedBits: number
newValues: number
}) {
super()
assertIdWithinFamily(userId)
if ((!Number.isSafeInteger(modifiedBits)) || (!Number.isSafeInteger(newValues))) {
throw new Error('flags must be an integer')
}
if ((modifiedBits | UserFlags.ALL_FLAGS) !== UserFlags.ALL_FLAGS || (modifiedBits | newValues) !== modifiedBits) {
throw new Error('flags are out of the valid range')
}
this.userId = userId
this.modifiedBits = modifiedBits
this.newValues = newValues
}
serialize = (): SerializedUpdateUserFlagsAction => ({
type: 'UPDATE_USER_FLAGS',
userId: this.userId,
modified: this.modifiedBits,
values: this.newValues
})
static parse = ({ userId, modified, values }: SerializedUpdateUserFlagsAction) => (
new UpdateUserFlagsAction({
userId,
modifiedBits: modified,
newValues: values
})
)
}
export interface SerializedUpdateUserFlagsAction {
type: 'UPDATE_USER_FLAGS'
userId: string
modified: number
values: number
}
+34
View File
@@ -1125,6 +1125,33 @@ const definitions = {
"type"
]
},
"SerializedUpdateUserFlagsAction": {
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": [
"UPDATE_USER_FLAGS"
]
},
"userId": {
"type": "string"
},
"modified": {
"type": "number"
},
"values": {
"type": "number"
}
},
"additionalProperties": false,
"required": [
"modified",
"type",
"userId",
"values"
]
},
"SerializedAddInstalledAppsAction": {
"type": "object",
"properties": {
@@ -2049,6 +2076,9 @@ const definitions = {
},
"blockedTimes": {
"type": "string"
},
"flags": {
"type": "number"
}
},
"additionalProperties": false,
@@ -2057,6 +2087,7 @@ const definitions = {
"categoryForNotAssignedApps",
"currentDevice",
"disableLimitsUntil",
"flags",
"id",
"mail",
"mailNotificationFlags",
@@ -2384,6 +2415,9 @@ export const isSerializedParentAction: (value: object) => value is SerializedPar
},
{
"$ref": "#/definitions/SerializedUpdateTimelimitRuleAction"
},
{
"$ref": "#/definitions/SerializedUpdateUserFlagsAction"
}
],
"definitions": definitions,
@@ -0,0 +1,39 @@
/*
* server component for the TimeLimit App
* Copyright (C) 2019 - 2020 Jonas Lochmann
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { QueryInterface, Sequelize, Transaction } from 'sequelize'
import { attributesVersion6 as userAttributes } from '../../user'
export async function up (queryInterface: QueryInterface, sequelize: Sequelize) {
await sequelize.transaction({
type: Transaction.TYPES.EXCLUSIVE
}, async (transaction) => {
await queryInterface.addColumn('Users', 'flags', {
...userAttributes.flags
}, {
transaction
})
})
}
export async function down (queryInterface: QueryInterface, sequelize: Sequelize) {
await sequelize.transaction({
type: Transaction.TYPES.EXCLUSIVE
}, async (transaction) => {
await queryInterface.removeColumn('Users', 'flags', { transaction })
})
}
+21 -3
View File
@@ -1,6 +1,6 @@
/*
* server component for the TimeLimit App
* Copyright (C) 2019 Jonas Lochmann
* Copyright (C) 2019 - 2020 Jonas Lochmann
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
@@ -16,6 +16,7 @@
*/
import * as Sequelize from 'sequelize'
import { UserFlags } from '../model/userflags'
import { serializedBitmaskRegex } from '../util/bitmask'
import { optionalPasswordRegex, optionalSaltRegex } from '../util/password'
import { booleanColumn, createEnumColumn, familyIdColumn, idWithinFamilyColumn, labelColumn, optionalIdWithinFamilyColumn, timestampColumn } from './columns'
@@ -54,8 +55,12 @@ export interface UserAttributesVersion5 {
blockedTimes: string
}
export interface UserAttributesVersion6 {
flags: string
}
export type UserAttributes = UserAttributesVersion1 & UserAttributesVersion2 &
UserAttributesVersion3 & UserAttributesVersion4 & UserAttributesVersion5
UserAttributesVersion3 & UserAttributesVersion4 & UserAttributesVersion5 & UserAttributesVersion6
export type UserModel = Sequelize.Model & UserAttributes
export type UserModelStatic = typeof Sequelize.Model & {
@@ -139,12 +144,25 @@ export const attributesVersion5: SequelizeAttributes<UserAttributesVersion5> = {
}
}
export const attributesVersion6: SequelizeAttributes<UserAttributesVersion6> = {
flags: {
type: Sequelize.BIGINT,
allowNull: false,
defaultValue: 0,
validate: {
min: 0,
max: UserFlags.ALL_FLAGS
}
}
}
export const attributes: SequelizeAttributes<UserAttributes> = {
...attributesVersion1,
...attributesVersion2,
...attributesVersion3,
...attributesVersion4,
...attributesVersion5
...attributesVersion5,
...attributesVersion6
}
export const createUserModel = (sequelize: Sequelize.Sequelize): UserModelStatic => sequelize.define('User', attributes) as UserModelStatic
+2 -1
View File
@@ -82,7 +82,8 @@ export const createFamily = async ({ database, mailAuthToken, firstParentDevice,
categoryForNotAssignedApps: '',
relaxPrimaryDeviceRule: false,
mailNotificationFlags: 1, // enable warning notifications
blockedTimes: ''
blockedTimes: '',
flags: '0'
}, { transaction })
// add parent device
@@ -1,6 +1,6 @@
/*
* server component for the TimeLimit App
* Copyright (C) 2019 Jonas Lochmann
* Copyright (C) 2019 - 2020 Jonas Lochmann
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
@@ -37,7 +37,8 @@ export async function dispatchAddUser ({ action, cache }: {
categoryForNotAssignedApps: '',
relaxPrimaryDeviceRule: false,
mailNotificationFlags: 0,
blockedTimes: ''
blockedTimes: '',
flags: '0'
}, { transaction: cache.transaction })
cache.invalidiateUserList = true
@@ -55,7 +55,8 @@ import {
UpdateNetworkTimeVerificationAction,
UpdateParentBlockedTimesAction,
UpdateParentNotificationFlagsAction,
UpdateTimelimitRuleAction
UpdateTimelimitRuleAction,
UpdateUserFlagsAction
} from '../../../../action'
import { Cache } from '../cache'
import { dispatchAddCategoryApps } from './addcategoryapps'
@@ -97,6 +98,7 @@ import { dispatchUpdateNetworkTimeVerification } from './updatenetworktimeverifi
import { dispatchUpdateParentBlockedTimes } from './updateparentblockedtimes'
import { dispatchUpdateParentNotificationFlags } from './updateparentnotificationflags'
import { dispatchUpdateTimelimitRule } from './updatetimelimitrule'
import { dispatchUpdateUserFlagsAction } from './updateuserflags'
export const dispatchParentAction = async ({ action, cache, parentUserId, sourceDeviceId }: {
action: ParentAction
@@ -182,6 +184,8 @@ export const dispatchParentAction = async ({ action, cache, parentUserId, source
await dispatchResetParentBlockedTimes({ action, cache })
} else if (action instanceof UpdateParentBlockedTimesAction) {
await dispatchUpdateParentBlockedTimes({ action, cache })
} else if (action instanceof UpdateUserFlagsAction) {
await dispatchUpdateUserFlagsAction({ action, cache })
} else {
throw new Error('unsupported action type')
}
@@ -0,0 +1,50 @@
/*
* server component for the TimeLimit App
* Copyright (C) 2019 Jonas Lochmann
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { UpdateUserFlagsAction } from '../../../../action'
import { Cache } from '../cache'
export async function dispatchUpdateUserFlagsAction ({ action, cache }: {
action: UpdateUserFlagsAction
cache: Cache
}) {
const userEntry = await cache.database.user.findOne({
where: {
familyId: cache.familyId,
userId: action.userId
},
transaction: cache.transaction
})
if (!userEntry) {
throw new Error('user not found')
}
const oldFlags = parseInt(userEntry.flags, 10)
if (!Number.isSafeInteger(oldFlags)) {
throw new Error()
}
const newFlags = (oldFlags & ~action.modifiedBits) | action.newValues
userEntry.flags = newFlags.toString(10)
await userEntry.save({ transaction: cache.transaction })
cache.invalidiateUserList = true
}
+6 -3
View File
@@ -125,7 +125,8 @@ export const generateServerDataStatus = async ({ database, clientStatus, familyI
'categoryForNotAssignedApps',
'relaxPrimaryDeviceRule',
'mailNotificationFlags',
'blockedTimes'
'blockedTimes',
'flags'
],
transaction
})).map((item) => ({
@@ -141,7 +142,8 @@ export const generateServerDataStatus = async ({ database, clientStatus, familyI
categoryForNotAssignedApps: item.categoryForNotAssignedApps,
relaxPrimaryDeviceRule: item.relaxPrimaryDeviceRule,
mailNotificationFlags: item.mailNotificationFlags,
blockedTimes: item.blockedTimes
blockedTimes: item.blockedTimes,
flags: item.flags
}))
result.users = {
@@ -159,7 +161,8 @@ export const generateServerDataStatus = async ({ database, clientStatus, familyI
categoryForNotAssignedApps: item.categoryForNotAssignedApps,
relaxPrimaryDevice: item.relaxPrimaryDeviceRule,
mailNotificationFlags: item.mailNotificationFlags,
blockedTimes: item.blockedTimes
blockedTimes: item.blockedTimes,
flags: parseInt(item.flags, 10)
}))
}
}
+21
View File
@@ -0,0 +1,21 @@
/*
* server component for the TimeLimit App
* Copyright (C) 2019 - 2020 Jonas Lochmann
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
export const UserFlags = {
RESTRICT_VIEWING_TO_PARENTS: 1,
ALL_FLAGS: 1
}
+1
View File
@@ -58,6 +58,7 @@ export interface ServerUserEntry {
relaxPrimaryDevice: boolean
mailNotificationFlags: number
blockedTimes: string
flags: number
}
export interface ServerDeviceData {