mirror of
https://codeberg.org/timelimit/timelimit-server.git
synced 2026-08-31 19:03:45 +02:00
Add support for child users adding limits themself
This commit is contained in:
@@ -44,6 +44,7 @@ The integrity field of a action may have got one of the following values:
|
||||
- an empty string when no user authentication is required/ for app logic actions (e.g. incrementing the used time)
|
||||
- the string ``device`` in case of parent actions if a parent is assigned to the device and asking for the password was disabled
|
||||
- ``sha512(sequence number as string with the base 10 + the device id as string + the hash of the user password using the second salt as string + the encoded action as string)`` for parent and child actions
|
||||
- the string ``childDevice`` in case the child wants to add limits for itself using parent actions; this feature must be enabled for the child and this allows only some actions with some parameters
|
||||
|
||||
In case of a invalid integrity value, the action is ignored and the client is told to do a full sync
|
||||
|
||||
|
||||
@@ -18,11 +18,13 @@
|
||||
import * as Sequelize from 'sequelize'
|
||||
import { AddCategoryAppsAction } from '../../../../action'
|
||||
import { CategoryAppAttributes } from '../../../../database/categoryapp'
|
||||
import { getCategoryWithParentCategories } from '../../../../util/category'
|
||||
import { Cache } from '../cache'
|
||||
|
||||
export async function dispatchAddCategoryApps ({ action, cache }: {
|
||||
export async function dispatchAddCategoryApps ({ action, cache, fromChildSelfLimitAddChildUserId }: {
|
||||
action: AddCategoryAppsAction
|
||||
cache: Cache
|
||||
fromChildSelfLimitAddChildUserId: string | null
|
||||
}) {
|
||||
const categoryEntryUnsafe = await cache.database.category.findOne({
|
||||
where: {
|
||||
@@ -39,14 +41,25 @@ export async function dispatchAddCategoryApps ({ action, cache }: {
|
||||
|
||||
const { childId } = categoryEntryUnsafe
|
||||
|
||||
if (fromChildSelfLimitAddChildUserId !== null) {
|
||||
if (childId !== fromChildSelfLimitAddChildUserId) {
|
||||
throw new Error('can not add apps to other users')
|
||||
}
|
||||
}
|
||||
|
||||
const categoriesOfSameChild = await cache.database.category.findAll({
|
||||
where: {
|
||||
familyId: cache.familyId,
|
||||
childId
|
||||
},
|
||||
attributes: ['categoryId'],
|
||||
attributes: ['categoryId', 'parentCategoryId'],
|
||||
transaction: cache.transaction
|
||||
}).map((item) => ({ categoryId: item.categoryId }))
|
||||
}).map((item) => ({
|
||||
categoryId: item.categoryId,
|
||||
parentCategoryId: item.parentCategoryId
|
||||
}))
|
||||
|
||||
const userCategoryIds = categoriesOfSameChild.map((item) => item.categoryId)
|
||||
|
||||
const oldCategories = await cache.database.categoryApp.findAll({
|
||||
attributes: [ 'categoryId' ],
|
||||
@@ -54,7 +67,7 @@ export async function dispatchAddCategoryApps ({ action, cache }: {
|
||||
where: {
|
||||
familyId: cache.familyId,
|
||||
categoryId: {
|
||||
[Sequelize.Op.in]: categoriesOfSameChild.map((item) => item.categoryId)
|
||||
[Sequelize.Op.in]: userCategoryIds
|
||||
},
|
||||
packageName: {
|
||||
[Sequelize.Op.in]: action.packageNames
|
||||
@@ -63,6 +76,68 @@ export async function dispatchAddCategoryApps ({ action, cache }: {
|
||||
transaction: cache.transaction
|
||||
}).map((item) => item.categoryId)
|
||||
|
||||
if (fromChildSelfLimitAddChildUserId !== null) {
|
||||
const parentCategoriesOfTargetCategory = getCategoryWithParentCategories(categoriesOfSameChild, action.categoryId)
|
||||
const userEntryUnsafe = await cache.database.user.findOne({
|
||||
attributes: [ 'categoryForNotAssignedApps' ],
|
||||
where: {
|
||||
familyId: cache.familyId,
|
||||
userId: fromChildSelfLimitAddChildUserId
|
||||
},
|
||||
transaction: cache.transaction
|
||||
})
|
||||
|
||||
if (!userEntryUnsafe) {
|
||||
throw new Error('illegal state')
|
||||
}
|
||||
|
||||
const userEntry = { categoryForNotAssignedApps: userEntryUnsafe.categoryForNotAssignedApps }
|
||||
const validatedDefaultCategoryId = categoriesOfSameChild.find((item) => item.categoryId === userEntry.categoryForNotAssignedApps)?.categoryId
|
||||
const allowUnassignedElements = validatedDefaultCategoryId !== undefined &&
|
||||
parentCategoriesOfTargetCategory.indexOf(validatedDefaultCategoryId) !== -1
|
||||
|
||||
const assertCanAddApp = async (packageName: string, isApp: boolean) => {
|
||||
const categoryAppEntryUnsafe = await cache.database.categoryApp.findOne({
|
||||
attributes: [ 'categoryId' ],
|
||||
where: {
|
||||
familyId: cache.familyId,
|
||||
categoryId: {
|
||||
[Sequelize.Op.in]: userCategoryIds
|
||||
},
|
||||
packageName: packageName
|
||||
},
|
||||
transaction: cache.transaction
|
||||
})
|
||||
|
||||
const categoryAppEntry = categoryAppEntryUnsafe ? { categoryId: categoryAppEntryUnsafe.categoryId } : null
|
||||
|
||||
if (categoryAppEntry === null) {
|
||||
if ((isApp && allowUnassignedElements) || (!isApp)) {
|
||||
// allow
|
||||
} else {
|
||||
throw new Error('can not assign apps without category as child')
|
||||
}
|
||||
} else {
|
||||
if (parentCategoriesOfTargetCategory.indexOf(categoryAppEntry.categoryId) !== -1) {
|
||||
// allow
|
||||
} else {
|
||||
throw new Error('can not add app which is not contained in the parent category')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (let i = 0; i < action.packageNames.length; i++) {
|
||||
const packageName = action.packageNames[i]
|
||||
|
||||
if (packageName.indexOf(':') !== -1) {
|
||||
await assertCanAddApp(packageName.substring(0, packageName.indexOf(':')), true)
|
||||
await assertCanAddApp(packageName, false)
|
||||
} else {
|
||||
await assertCanAddApp(packageName, true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (oldCategories.length > 0) {
|
||||
await cache.database.categoryApp.destroy({
|
||||
where: {
|
||||
|
||||
@@ -19,10 +19,17 @@ import { CreateCategoryAction } from '../../../../action'
|
||||
import { generateVersionId } from '../../../../util/token'
|
||||
import { Cache } from '../cache'
|
||||
|
||||
export async function dispatchCreateCategory ({ action, cache }: {
|
||||
export async function dispatchCreateCategory ({ action, cache, fromChildSelfLimitAddChildUserId }: {
|
||||
action: CreateCategoryAction
|
||||
cache: Cache
|
||||
fromChildSelfLimitAddChildUserId: string | null
|
||||
}) {
|
||||
if (fromChildSelfLimitAddChildUserId !== null) {
|
||||
if (fromChildSelfLimitAddChildUserId !== action.childId) {
|
||||
throw new Error('can not create categories for other child users')
|
||||
}
|
||||
}
|
||||
|
||||
// check that the child exists
|
||||
const childEntry = await cache.database.user.findOne({
|
||||
where: {
|
||||
|
||||
@@ -18,16 +18,30 @@
|
||||
import { CreateTimeLimitRuleAction } from '../../../../action'
|
||||
import { Cache } from '../cache'
|
||||
|
||||
export async function dispatchCreateTimeLimitRule ({ action, cache }: {
|
||||
export async function dispatchCreateTimeLimitRule ({ action, cache, fromChildSelfLimitAddChildUserId }: {
|
||||
action: CreateTimeLimitRuleAction
|
||||
cache: Cache
|
||||
fromChildSelfLimitAddChildUserId: string | null
|
||||
}) {
|
||||
const doesCategoryExist = await cache.doesCategoryExist(action.rule.categoryId)
|
||||
const categoryEntryUnsafe = await cache.database.category.findOne({
|
||||
where: {
|
||||
familyId: cache.familyId,
|
||||
categoryId: action.rule.categoryId
|
||||
},
|
||||
transaction: cache.transaction,
|
||||
attributes: ['childId']
|
||||
})
|
||||
|
||||
if (!doesCategoryExist) {
|
||||
if (!categoryEntryUnsafe) {
|
||||
throw new Error('invalid category id for new rule')
|
||||
}
|
||||
|
||||
if (fromChildSelfLimitAddChildUserId !== null) {
|
||||
if (fromChildSelfLimitAddChildUserId !== categoryEntryUnsafe.childId) {
|
||||
throw new Error('can not add rules for other users')
|
||||
}
|
||||
}
|
||||
|
||||
await cache.database.timelimitRule.create({
|
||||
familyId: cache.familyId,
|
||||
ruleId: action.rule.ruleId,
|
||||
|
||||
@@ -102,94 +102,99 @@ import { dispatchUpdateTimelimitRule } from './updatetimelimitrule'
|
||||
import { dispatchUpdateUserFlagsAction } from './updateuserflags'
|
||||
import { dispatchUpdateUserLimitLoginCategoryAction } from './updateuserlimitlogincategory'
|
||||
|
||||
export const dispatchParentAction = async ({ action, cache, parentUserId, sourceDeviceId }: {
|
||||
export const dispatchParentAction = async ({ action, cache, parentUserId, sourceDeviceId, fromChildSelfLimitAddChildUserId }: {
|
||||
action: ParentAction
|
||||
cache: Cache
|
||||
parentUserId: string
|
||||
sourceDeviceId: string | null
|
||||
fromChildSelfLimitAddChildUserId: string | null
|
||||
}) => {
|
||||
if (action instanceof AddCategoryAppsAction) {
|
||||
await dispatchAddCategoryApps({ action, cache })
|
||||
} else if (action instanceof AddUserAction) {
|
||||
await dispatchAddUser({ action, cache })
|
||||
} else if (action instanceof RemoveCategoryAppsAction) {
|
||||
await dispatchRemoveCategoryApps({ action, cache })
|
||||
return dispatchAddCategoryApps({ action, cache, fromChildSelfLimitAddChildUserId })
|
||||
} else if (action instanceof CreateCategoryAction) {
|
||||
await dispatchCreateCategory({ action, cache })
|
||||
return dispatchCreateCategory({ action, cache, fromChildSelfLimitAddChildUserId })
|
||||
} else if (action instanceof CreateTimeLimitRuleAction) {
|
||||
await dispatchCreateTimeLimitRule({ action, cache })
|
||||
} else if (action instanceof DeleteCategoryAction) {
|
||||
await dispatchDeleteCategory({ action, cache })
|
||||
} else if (action instanceof UpdateCategoryTitleAction) {
|
||||
await dispatchUpdateCategoryTitle({ action, cache })
|
||||
} else if (action instanceof SetCategoryExtraTimeAction) {
|
||||
await dispatchSetCategoryExtraTime({ action, cache })
|
||||
} else if (action instanceof SetCategoryForUnassignedAppsAction) {
|
||||
await dispatchSetCategoryForUnassignedApps({ action, cache })
|
||||
} else if (action instanceof SetChildPasswordAction) {
|
||||
await dispatchSetChildPassword({ action, cache })
|
||||
} else if (action instanceof SetConsiderRebootManipulationAction) {
|
||||
await dispatchSetConsiderRebootManipulation({ action, cache })
|
||||
} else if (action instanceof SetDeviceDefaultUserAction) {
|
||||
await dispatchSetDeviceDefaultUser({ action, cache })
|
||||
} else if (action instanceof SetDeviceDefaultUserTimeoutAction) {
|
||||
await dispatchSetDeviceDefaultUserTimeout({ action, cache })
|
||||
} else if (action instanceof SetDeviceUserAction) {
|
||||
await dispatchSetDeviceUser({ action, cache })
|
||||
} else if (action instanceof SetKeepSignedInAction) {
|
||||
await dispatchSetKeepSignedIn({ action, cache, parentUserId })
|
||||
} else if (action instanceof SetParentCategoryAction) {
|
||||
await dispatchSetParentCategory({ action, cache })
|
||||
} else if (action instanceof SetRelaxPrimaryDeviceAction) {
|
||||
await dispatchSetRelaxPrimaryDevice({ action, cache })
|
||||
} else if (action instanceof SetSendDeviceConnected) {
|
||||
await dispatchSetSendDeviceConnected({ action, cache, sourceDeviceId })
|
||||
} else if (action instanceof SetUserDisableLimitsUntilAction) {
|
||||
await dispatchUserSetDisableLimitsUntil({ action, cache })
|
||||
} else if (action instanceof SetUserTimezoneAction) {
|
||||
await dispatchSetUserTimezone({ action, cache })
|
||||
} else if (action instanceof UpdateCategoryBatteryLimitAction) {
|
||||
await dispatchUpdateCategoryBatteryLimit({ action, cache })
|
||||
return dispatchCreateTimeLimitRule({ action, cache, fromChildSelfLimitAddChildUserId })
|
||||
} else if (action instanceof UpdateCategoryBlockAllNotificationsAction) {
|
||||
await dispatchUpdateCategoryBlockAllNotifications({ action, cache })
|
||||
} else if (action instanceof UpdateCategoryBlockedTimesAction) {
|
||||
await dispatchUpdateCategoryBlockedTimes({ action, cache })
|
||||
} else if (action instanceof UpdateCategorySortingAction) {
|
||||
await dispatchUpdateCategorySorting({ action, cache })
|
||||
} else if (action instanceof IncrementCategoryExtraTimeAction) {
|
||||
await dispatchIncrementCategoryExtraTime({ action, cache })
|
||||
return dispatchUpdateCategoryBlockAllNotifications({ action, cache, fromChildSelfLimitAddChildUserId })
|
||||
} else if (action instanceof SetParentCategoryAction) {
|
||||
return dispatchSetParentCategory({ action, cache, fromChildSelfLimitAddChildUserId })
|
||||
} else if (action instanceof UpdateCategoryTemporarilyBlockedAction) {
|
||||
await dispatchUpdateCategoryTemporarilyBlocked({ action, cache })
|
||||
} else if (action instanceof DeleteTimeLimitRuleAction) {
|
||||
await dispatchDeleteTimeLimitRule({ action, cache })
|
||||
} else if (action instanceof UpdateDeviceNameAction) {
|
||||
await dispatchUpdateDeviceName({ action, cache })
|
||||
} else if (action instanceof UpdateEnableActivityLevelBlockingAction) {
|
||||
await dispatchUpdateEnableActivityLevelBlocking({ action, cache })
|
||||
} else if (action instanceof UpdateNetworkTimeVerificationAction) {
|
||||
await dispatchUpdateNetworkTimeVerification({ action, cache })
|
||||
} else if (action instanceof UpdateParentNotificationFlagsAction) {
|
||||
await dispatchUpdateParentNotificationFlags({ action, cache })
|
||||
} else if (action instanceof UpdateTimelimitRuleAction) {
|
||||
await dispatchUpdateTimelimitRule({ action, cache })
|
||||
} else if (action instanceof RemoveUserAction) {
|
||||
await dispatchRemoveUser({ action, cache, parentUserId })
|
||||
} else if (action instanceof RenameChildAction) {
|
||||
await dispatchRenameChild({ action, cache })
|
||||
} else if (action instanceof ChangeParentPasswordAction) {
|
||||
await dispatchChangeParentPassword({ action, cache })
|
||||
} else if (action instanceof IgnoreManipulationAction) {
|
||||
await dispatchIgnoreManipulation({ action, cache })
|
||||
} else if (action instanceof UpdateCategoryTimeWarningsAction) {
|
||||
await dispatchUpdateCategoryTimeWarnings({ action, cache })
|
||||
} else if (action instanceof ResetParentBlockedTimesAction) {
|
||||
await dispatchResetParentBlockedTimes({ action, cache })
|
||||
} else if (action instanceof UpdateParentBlockedTimesAction) {
|
||||
await dispatchUpdateParentBlockedTimes({ action, cache, parentUserId })
|
||||
} else if (action instanceof UpdateUserFlagsAction) {
|
||||
await dispatchUpdateUserFlagsAction({ action, cache })
|
||||
} else if (action instanceof UpdateUserLimitLoginCategory) {
|
||||
await dispatchUpdateUserLimitLoginCategoryAction({ action, cache, parentUserId })
|
||||
return dispatchUpdateCategoryTemporarilyBlocked({ action, cache, fromChildSelfLimitAddChildUserId })
|
||||
}
|
||||
|
||||
if (fromChildSelfLimitAddChildUserId === null) {
|
||||
if (action instanceof AddUserAction) {
|
||||
return dispatchAddUser({ action, cache })
|
||||
} else if (action instanceof RemoveCategoryAppsAction) {
|
||||
return dispatchRemoveCategoryApps({ action, cache })
|
||||
} else if (action instanceof DeleteCategoryAction) {
|
||||
return dispatchDeleteCategory({ action, cache })
|
||||
} else if (action instanceof UpdateCategoryTitleAction) {
|
||||
return dispatchUpdateCategoryTitle({ action, cache })
|
||||
} else if (action instanceof SetCategoryExtraTimeAction) {
|
||||
return dispatchSetCategoryExtraTime({ action, cache })
|
||||
} else if (action instanceof SetCategoryForUnassignedAppsAction) {
|
||||
return dispatchSetCategoryForUnassignedApps({ action, cache })
|
||||
} else if (action instanceof SetChildPasswordAction) {
|
||||
return dispatchSetChildPassword({ action, cache })
|
||||
} else if (action instanceof SetConsiderRebootManipulationAction) {
|
||||
return dispatchSetConsiderRebootManipulation({ action, cache })
|
||||
} else if (action instanceof SetDeviceDefaultUserAction) {
|
||||
return dispatchSetDeviceDefaultUser({ action, cache })
|
||||
} else if (action instanceof SetDeviceDefaultUserTimeoutAction) {
|
||||
return dispatchSetDeviceDefaultUserTimeout({ action, cache })
|
||||
} else if (action instanceof SetDeviceUserAction) {
|
||||
return dispatchSetDeviceUser({ action, cache })
|
||||
} else if (action instanceof SetKeepSignedInAction) {
|
||||
return dispatchSetKeepSignedIn({ action, cache, parentUserId })
|
||||
} else if (action instanceof SetRelaxPrimaryDeviceAction) {
|
||||
return dispatchSetRelaxPrimaryDevice({ action, cache })
|
||||
} else if (action instanceof SetSendDeviceConnected) {
|
||||
return dispatchSetSendDeviceConnected({ action, cache, sourceDeviceId })
|
||||
} else if (action instanceof SetUserDisableLimitsUntilAction) {
|
||||
return dispatchUserSetDisableLimitsUntil({ action, cache })
|
||||
} else if (action instanceof SetUserTimezoneAction) {
|
||||
return dispatchSetUserTimezone({ action, cache })
|
||||
} else if (action instanceof UpdateCategoryBatteryLimitAction) {
|
||||
return dispatchUpdateCategoryBatteryLimit({ action, cache })
|
||||
} else if (action instanceof UpdateCategoryBlockedTimesAction) {
|
||||
return dispatchUpdateCategoryBlockedTimes({ action, cache })
|
||||
} else if (action instanceof UpdateCategorySortingAction) {
|
||||
return dispatchUpdateCategorySorting({ action, cache })
|
||||
} else if (action instanceof IncrementCategoryExtraTimeAction) {
|
||||
return dispatchIncrementCategoryExtraTime({ action, cache })
|
||||
} else if (action instanceof DeleteTimeLimitRuleAction) {
|
||||
return dispatchDeleteTimeLimitRule({ action, cache })
|
||||
} else if (action instanceof UpdateDeviceNameAction) {
|
||||
return dispatchUpdateDeviceName({ action, cache })
|
||||
} else if (action instanceof UpdateEnableActivityLevelBlockingAction) {
|
||||
return dispatchUpdateEnableActivityLevelBlocking({ action, cache })
|
||||
} else if (action instanceof UpdateNetworkTimeVerificationAction) {
|
||||
return dispatchUpdateNetworkTimeVerification({ action, cache })
|
||||
} else if (action instanceof UpdateParentNotificationFlagsAction) {
|
||||
return dispatchUpdateParentNotificationFlags({ action, cache })
|
||||
} else if (action instanceof UpdateTimelimitRuleAction) {
|
||||
return dispatchUpdateTimelimitRule({ action, cache })
|
||||
} else if (action instanceof RemoveUserAction) {
|
||||
return dispatchRemoveUser({ action, cache, parentUserId })
|
||||
} else if (action instanceof RenameChildAction) {
|
||||
return dispatchRenameChild({ action, cache })
|
||||
} else if (action instanceof ChangeParentPasswordAction) {
|
||||
return dispatchChangeParentPassword({ action, cache })
|
||||
} else if (action instanceof IgnoreManipulationAction) {
|
||||
return dispatchIgnoreManipulation({ action, cache })
|
||||
} else if (action instanceof UpdateCategoryTimeWarningsAction) {
|
||||
return dispatchUpdateCategoryTimeWarnings({ action, cache })
|
||||
} else if (action instanceof ResetParentBlockedTimesAction) {
|
||||
return dispatchResetParentBlockedTimes({ action, cache })
|
||||
} else if (action instanceof UpdateParentBlockedTimesAction) {
|
||||
return dispatchUpdateParentBlockedTimes({ action, cache, parentUserId })
|
||||
} else if (action instanceof UpdateUserFlagsAction) {
|
||||
return dispatchUpdateUserFlagsAction({ action, cache })
|
||||
} else if (action instanceof UpdateUserLimitLoginCategory) {
|
||||
return dispatchUpdateUserLimitLoginCategoryAction({ action, cache, parentUserId })
|
||||
}
|
||||
} else {
|
||||
throw new Error('unsupported action type')
|
||||
}
|
||||
|
||||
@@ -16,11 +16,13 @@
|
||||
*/
|
||||
|
||||
import { SetParentCategoryAction } from '../../../../action'
|
||||
import { getCategoryWithParentCategories } from '../../../../util/category'
|
||||
import { Cache } from '../cache'
|
||||
|
||||
export async function dispatchSetParentCategory ({ action, cache }: {
|
||||
export async function dispatchSetParentCategory ({ action, cache, fromChildSelfLimitAddChildUserId }: {
|
||||
action: SetParentCategoryAction
|
||||
cache: Cache
|
||||
fromChildSelfLimitAddChildUserId: string | null
|
||||
}) {
|
||||
const categoryEntry = await cache.database.category.findOne({
|
||||
where: {
|
||||
@@ -34,6 +36,12 @@ export async function dispatchSetParentCategory ({ action, cache }: {
|
||||
throw new Error('tried to set parent category of non existent category')
|
||||
}
|
||||
|
||||
if (fromChildSelfLimitAddChildUserId !== null) {
|
||||
if (categoryEntry.childId !== fromChildSelfLimitAddChildUserId) {
|
||||
throw new Error('can not set parent category for other user')
|
||||
}
|
||||
}
|
||||
|
||||
if (action.parentCategory !== '') {
|
||||
const categoriesByUserId = (await cache.database.category.findAll({
|
||||
where: {
|
||||
@@ -74,6 +82,16 @@ export async function dispatchSetParentCategory ({ action, cache }: {
|
||||
if (childCategoryIds.has(action.parentCategory) || action.parentCategory === action.categoryId) {
|
||||
throw new Error('can not set a category as parent which is a child of the category')
|
||||
}
|
||||
|
||||
if (fromChildSelfLimitAddChildUserId !== null) {
|
||||
const ownParentCategory = categoriesByUserId.find((item) => item.categoryId === categoryEntry.parentCategoryId)
|
||||
const enableDueToLimitAddingWhenChild = ownParentCategory === undefined ||
|
||||
getCategoryWithParentCategories(categoriesByUserId, action.parentCategory).indexOf(ownParentCategory.categoryId) !== -1
|
||||
|
||||
if (!enableDueToLimitAddingWhenChild) {
|
||||
throw new Error('can not change parent categories in a way which reduces limits')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await cache.database.category.update({
|
||||
|
||||
+26
-2
@@ -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
|
||||
@@ -18,10 +18,34 @@
|
||||
import { UpdateCategoryBlockAllNotificationsAction } from '../../../../action'
|
||||
import { Cache } from '../cache'
|
||||
|
||||
export async function dispatchUpdateCategoryBlockAllNotifications ({ action, cache }: {
|
||||
export async function dispatchUpdateCategoryBlockAllNotifications ({ action, cache, fromChildSelfLimitAddChildUserId }: {
|
||||
action: UpdateCategoryBlockAllNotificationsAction
|
||||
cache: Cache
|
||||
fromChildSelfLimitAddChildUserId: string | null
|
||||
}) {
|
||||
const categoryEntryUnsafe = await cache.database.category.findOne({
|
||||
where: {
|
||||
familyId: cache.familyId,
|
||||
categoryId: action.categoryId
|
||||
},
|
||||
transaction: cache.transaction,
|
||||
attributes: ['childId']
|
||||
})
|
||||
|
||||
if (!categoryEntryUnsafe) {
|
||||
throw new Error('invalid category id for updating notification blocking')
|
||||
}
|
||||
|
||||
if (fromChildSelfLimitAddChildUserId !== null) {
|
||||
if (fromChildSelfLimitAddChildUserId !== categoryEntryUnsafe.childId) {
|
||||
throw new Error('can not add rules for other users')
|
||||
}
|
||||
|
||||
if (!action.blocked) {
|
||||
throw new Error('can not disable filter as child')
|
||||
}
|
||||
}
|
||||
|
||||
const [affectedRows] = await cache.database.category.update({
|
||||
blockAllNotifications: action.blocked
|
||||
}, {
|
||||
|
||||
+37
-1
@@ -18,9 +18,10 @@
|
||||
import { UpdateCategoryTemporarilyBlockedAction } from '../../../../action'
|
||||
import { Cache } from '../cache'
|
||||
|
||||
export async function dispatchUpdateCategoryTemporarilyBlocked ({ action, cache }: {
|
||||
export async function dispatchUpdateCategoryTemporarilyBlocked ({ action, cache, fromChildSelfLimitAddChildUserId }: {
|
||||
action: UpdateCategoryTemporarilyBlockedAction
|
||||
cache: Cache
|
||||
fromChildSelfLimitAddChildUserId: string | null
|
||||
}) {
|
||||
if (action.blocked === true) {
|
||||
if (!cache.hasFullVersion) {
|
||||
@@ -28,6 +29,41 @@ export async function dispatchUpdateCategoryTemporarilyBlocked ({ action, cache
|
||||
}
|
||||
}
|
||||
|
||||
const categoryEntryUnsafe = await cache.database.category.findOne({
|
||||
where: {
|
||||
familyId: cache.familyId,
|
||||
categoryId: action.categoryId
|
||||
},
|
||||
transaction: cache.transaction,
|
||||
attributes: ['childId', 'temporarilyBlocked', 'temporarilyBlockedEndTime']
|
||||
})
|
||||
|
||||
if (!categoryEntryUnsafe) {
|
||||
throw new Error('invalid category id for updating temporarily blocking')
|
||||
}
|
||||
|
||||
const categoryEntry = {
|
||||
childId: categoryEntryUnsafe.childId,
|
||||
temporarilyBlocked: categoryEntryUnsafe.temporarilyBlocked,
|
||||
temporarilyBlockedEndTime: categoryEntryUnsafe.temporarilyBlockedEndTime
|
||||
}
|
||||
|
||||
if (fromChildSelfLimitAddChildUserId !== null) {
|
||||
if (fromChildSelfLimitAddChildUserId !== categoryEntry.childId) {
|
||||
throw new Error('can not update temporarily blocking as child for other users')
|
||||
}
|
||||
|
||||
if (action.endTime === undefined || !action.blocked) {
|
||||
throw new Error('the child may only enable a temporarily blocking')
|
||||
}
|
||||
|
||||
if (categoryEntry.temporarilyBlocked) {
|
||||
if (action.endTime < categoryEntry.temporarilyBlockedEndTime || categoryEntry.temporarilyBlockedEndTime === 0) {
|
||||
throw new Error('the child may not reduce the temporarily blocking')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const [affectedRows] = await cache.database.category.update({
|
||||
temporarilyBlocked: action.blocked,
|
||||
temporarilyBlockedEndTime: action.blocked ? (action.endTime ?? 0) : 0
|
||||
|
||||
@@ -22,6 +22,7 @@ import { ClientPushChangesRequest } from '../../../api/schema'
|
||||
import { isSerializedAppLogicAction, isSerializedChildAction, isSerializedParentAction } from '../../../api/validator'
|
||||
import { VisibleConnectedDevicesManager } from '../../../connected-devices'
|
||||
import { Database } from '../../../database'
|
||||
import { UserFlags } from '../../../model/userflags'
|
||||
import { EventHandler } from '../../../monitoring/eventhandler'
|
||||
import { WebsocketApi } from '../../../websocket'
|
||||
import { notifyClientsAboutChanges } from '../../websocket'
|
||||
@@ -106,6 +107,8 @@ export const applyActionsFromDevice = async ({ database, request, websocket, con
|
||||
// update the next sequence number
|
||||
nextSequenceNumber = action.sequenceNumber + 1
|
||||
|
||||
let isChildLimitAdding = false
|
||||
|
||||
if (action.type === 'parent') {
|
||||
if (action.integrity === 'device') {
|
||||
const deviceEntryUnsafe2 = await cache.database.device.findOne({
|
||||
@@ -125,6 +128,9 @@ export const applyActionsFromDevice = async ({ database, request, websocket, con
|
||||
|
||||
// this ensures that the parent exists
|
||||
await cache.getSecondPasswordHashOfParent(action.userId)
|
||||
} else if (action.integrity === 'childDevice') {
|
||||
// will be checked later
|
||||
isChildLimitAdding = true
|
||||
} else {
|
||||
const parentSecondHash = await cache.getSecondPasswordHashOfParent(action.userId)
|
||||
|
||||
@@ -191,19 +197,68 @@ export const applyActionsFromDevice = async ({ database, request, websocket, con
|
||||
throw new Error('invalid action' + action.encodedAction)
|
||||
}
|
||||
|
||||
eventHandler.countEvent('applyActionsFromDevice action:' + parsedSerializedAction.type)
|
||||
eventHandler.countEvent('applyActionsFromDevice, childAddLimit: ' + isChildLimitAdding + ' action:' + parsedSerializedAction.type)
|
||||
|
||||
const parsedAction = parseParentAction(parsedSerializedAction)
|
||||
|
||||
try {
|
||||
await dispatchParentAction({
|
||||
action: parsedAction,
|
||||
cache,
|
||||
parentUserId: action.userId,
|
||||
sourceDeviceId: deviceEntry.deviceId
|
||||
})
|
||||
if (isChildLimitAdding) {
|
||||
const deviceEntryUnsafe2 = await cache.database.device.findOne({
|
||||
attributes: ['currentUserId'],
|
||||
where: {
|
||||
familyId: cache.familyId,
|
||||
deviceId: deviceEntry.deviceId,
|
||||
currentUserId: action.userId
|
||||
},
|
||||
transaction: cache.transaction
|
||||
})
|
||||
|
||||
if (!deviceEntryUnsafe2) {
|
||||
throw new Error('illegal state')
|
||||
}
|
||||
|
||||
const deviceUserId = deviceEntryUnsafe2.currentUserId
|
||||
|
||||
if (!deviceUserId) {
|
||||
throw new Error('no device user id set but child add self limit action requested')
|
||||
}
|
||||
|
||||
const deviceUserEntryUnsafe = await cache.database.user.findOne({
|
||||
attributes: ['flags'],
|
||||
where: {
|
||||
familyId: cache.familyId,
|
||||
userId: deviceUserId,
|
||||
type: 'child'
|
||||
},
|
||||
transaction: cache.transaction
|
||||
})
|
||||
|
||||
if (!deviceUserEntryUnsafe) {
|
||||
throw new Error('no child user found for child limit adding action')
|
||||
}
|
||||
|
||||
if ((parseInt(deviceUserEntryUnsafe.flags, 10) & UserFlags.ALLOW_SELF_LIMIT_ADD) !== UserFlags.ALLOW_SELF_LIMIT_ADD) {
|
||||
throw new Error('child add limit action found but not allowed')
|
||||
}
|
||||
|
||||
await dispatchParentAction({
|
||||
action: parsedAction,
|
||||
cache,
|
||||
parentUserId: action.userId,
|
||||
sourceDeviceId: deviceEntry.deviceId,
|
||||
fromChildSelfLimitAddChildUserId: deviceUserId
|
||||
})
|
||||
} else {
|
||||
await dispatchParentAction({
|
||||
action: parsedAction,
|
||||
cache,
|
||||
parentUserId: action.userId,
|
||||
sourceDeviceId: deviceEntry.deviceId,
|
||||
fromChildSelfLimitAddChildUserId: null
|
||||
})
|
||||
}
|
||||
} catch (ex) {
|
||||
eventHandler.countEvent('applyActionsFromDevice actionWithError:' + parsedSerializedAction.type)
|
||||
eventHandler.countEvent('applyActionsFromDeviceWithError, childAddLimit: ' + isChildLimitAdding + ' action:' + parsedSerializedAction.type)
|
||||
|
||||
throw ex
|
||||
}
|
||||
|
||||
@@ -17,5 +17,6 @@
|
||||
|
||||
export const UserFlags = {
|
||||
RESTRICT_VIEWING_TO_PARENTS: 1,
|
||||
ALL_FLAGS: 1
|
||||
ALLOW_SELF_LIMIT_ADD: 2,
|
||||
ALL_FLAGS: 1 | 2
|
||||
}
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* 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 function getCategoryWithParentCategories (categories: Array<{ categoryId: string, parentCategoryId: string }>, startCategoryId: string): Array<string> {
|
||||
const categoryById = new Map<string, { categoryId: string, parentCategoryId: string }>()
|
||||
|
||||
categories.forEach((category) => categoryById.set(category.categoryId, category))
|
||||
|
||||
const startCategory = categoryById.get(startCategoryId)
|
||||
|
||||
if (!startCategory) {
|
||||
throw new Error('start category not found')
|
||||
}
|
||||
|
||||
const categoryIds = [ startCategoryId ]
|
||||
|
||||
let currentCategory = categoryById.get(startCategory.parentCategoryId)
|
||||
|
||||
while (currentCategory !== undefined && categoryIds.indexOf(currentCategory.categoryId) === -1) {
|
||||
categoryIds.push(currentCategory.categoryId)
|
||||
|
||||
currentCategory = categoryById.get(currentCategory.parentCategoryId)
|
||||
}
|
||||
|
||||
return categoryIds
|
||||
}
|
||||
Reference in New Issue
Block a user