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)
|
- 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
|
- 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
|
- ``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
|
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 * as Sequelize from 'sequelize'
|
||||||
import { AddCategoryAppsAction } from '../../../../action'
|
import { AddCategoryAppsAction } from '../../../../action'
|
||||||
import { CategoryAppAttributes } from '../../../../database/categoryapp'
|
import { CategoryAppAttributes } from '../../../../database/categoryapp'
|
||||||
|
import { getCategoryWithParentCategories } from '../../../../util/category'
|
||||||
import { Cache } from '../cache'
|
import { Cache } from '../cache'
|
||||||
|
|
||||||
export async function dispatchAddCategoryApps ({ action, cache }: {
|
export async function dispatchAddCategoryApps ({ action, cache, fromChildSelfLimitAddChildUserId }: {
|
||||||
action: AddCategoryAppsAction
|
action: AddCategoryAppsAction
|
||||||
cache: Cache
|
cache: Cache
|
||||||
|
fromChildSelfLimitAddChildUserId: string | null
|
||||||
}) {
|
}) {
|
||||||
const categoryEntryUnsafe = await cache.database.category.findOne({
|
const categoryEntryUnsafe = await cache.database.category.findOne({
|
||||||
where: {
|
where: {
|
||||||
@@ -39,14 +41,25 @@ export async function dispatchAddCategoryApps ({ action, cache }: {
|
|||||||
|
|
||||||
const { childId } = categoryEntryUnsafe
|
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({
|
const categoriesOfSameChild = await cache.database.category.findAll({
|
||||||
where: {
|
where: {
|
||||||
familyId: cache.familyId,
|
familyId: cache.familyId,
|
||||||
childId
|
childId
|
||||||
},
|
},
|
||||||
attributes: ['categoryId'],
|
attributes: ['categoryId', 'parentCategoryId'],
|
||||||
transaction: cache.transaction
|
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({
|
const oldCategories = await cache.database.categoryApp.findAll({
|
||||||
attributes: [ 'categoryId' ],
|
attributes: [ 'categoryId' ],
|
||||||
@@ -54,7 +67,7 @@ export async function dispatchAddCategoryApps ({ action, cache }: {
|
|||||||
where: {
|
where: {
|
||||||
familyId: cache.familyId,
|
familyId: cache.familyId,
|
||||||
categoryId: {
|
categoryId: {
|
||||||
[Sequelize.Op.in]: categoriesOfSameChild.map((item) => item.categoryId)
|
[Sequelize.Op.in]: userCategoryIds
|
||||||
},
|
},
|
||||||
packageName: {
|
packageName: {
|
||||||
[Sequelize.Op.in]: action.packageNames
|
[Sequelize.Op.in]: action.packageNames
|
||||||
@@ -63,6 +76,68 @@ export async function dispatchAddCategoryApps ({ action, cache }: {
|
|||||||
transaction: cache.transaction
|
transaction: cache.transaction
|
||||||
}).map((item) => item.categoryId)
|
}).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) {
|
if (oldCategories.length > 0) {
|
||||||
await cache.database.categoryApp.destroy({
|
await cache.database.categoryApp.destroy({
|
||||||
where: {
|
where: {
|
||||||
|
|||||||
@@ -19,10 +19,17 @@ import { CreateCategoryAction } from '../../../../action'
|
|||||||
import { generateVersionId } from '../../../../util/token'
|
import { generateVersionId } from '../../../../util/token'
|
||||||
import { Cache } from '../cache'
|
import { Cache } from '../cache'
|
||||||
|
|
||||||
export async function dispatchCreateCategory ({ action, cache }: {
|
export async function dispatchCreateCategory ({ action, cache, fromChildSelfLimitAddChildUserId }: {
|
||||||
action: CreateCategoryAction
|
action: CreateCategoryAction
|
||||||
cache: Cache
|
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
|
// check that the child exists
|
||||||
const childEntry = await cache.database.user.findOne({
|
const childEntry = await cache.database.user.findOne({
|
||||||
where: {
|
where: {
|
||||||
|
|||||||
@@ -18,16 +18,30 @@
|
|||||||
import { CreateTimeLimitRuleAction } from '../../../../action'
|
import { CreateTimeLimitRuleAction } from '../../../../action'
|
||||||
import { Cache } from '../cache'
|
import { Cache } from '../cache'
|
||||||
|
|
||||||
export async function dispatchCreateTimeLimitRule ({ action, cache }: {
|
export async function dispatchCreateTimeLimitRule ({ action, cache, fromChildSelfLimitAddChildUserId }: {
|
||||||
action: CreateTimeLimitRuleAction
|
action: CreateTimeLimitRuleAction
|
||||||
cache: Cache
|
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')
|
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({
|
await cache.database.timelimitRule.create({
|
||||||
familyId: cache.familyId,
|
familyId: cache.familyId,
|
||||||
ruleId: action.rule.ruleId,
|
ruleId: action.rule.ruleId,
|
||||||
|
|||||||
@@ -102,94 +102,99 @@ import { dispatchUpdateTimelimitRule } from './updatetimelimitrule'
|
|||||||
import { dispatchUpdateUserFlagsAction } from './updateuserflags'
|
import { dispatchUpdateUserFlagsAction } from './updateuserflags'
|
||||||
import { dispatchUpdateUserLimitLoginCategoryAction } from './updateuserlimitlogincategory'
|
import { dispatchUpdateUserLimitLoginCategoryAction } from './updateuserlimitlogincategory'
|
||||||
|
|
||||||
export const dispatchParentAction = async ({ action, cache, parentUserId, sourceDeviceId }: {
|
export const dispatchParentAction = async ({ action, cache, parentUserId, sourceDeviceId, fromChildSelfLimitAddChildUserId }: {
|
||||||
action: ParentAction
|
action: ParentAction
|
||||||
cache: Cache
|
cache: Cache
|
||||||
parentUserId: string
|
parentUserId: string
|
||||||
sourceDeviceId: string | null
|
sourceDeviceId: string | null
|
||||||
|
fromChildSelfLimitAddChildUserId: string | null
|
||||||
}) => {
|
}) => {
|
||||||
if (action instanceof AddCategoryAppsAction) {
|
if (action instanceof AddCategoryAppsAction) {
|
||||||
await dispatchAddCategoryApps({ action, cache })
|
return dispatchAddCategoryApps({ action, cache, fromChildSelfLimitAddChildUserId })
|
||||||
} else if (action instanceof AddUserAction) {
|
|
||||||
await dispatchAddUser({ action, cache })
|
|
||||||
} else if (action instanceof RemoveCategoryAppsAction) {
|
|
||||||
await dispatchRemoveCategoryApps({ action, cache })
|
|
||||||
} else if (action instanceof CreateCategoryAction) {
|
} else if (action instanceof CreateCategoryAction) {
|
||||||
await dispatchCreateCategory({ action, cache })
|
return dispatchCreateCategory({ action, cache, fromChildSelfLimitAddChildUserId })
|
||||||
} else if (action instanceof CreateTimeLimitRuleAction) {
|
} else if (action instanceof CreateTimeLimitRuleAction) {
|
||||||
await dispatchCreateTimeLimitRule({ action, cache })
|
return dispatchCreateTimeLimitRule({ action, cache, fromChildSelfLimitAddChildUserId })
|
||||||
} 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 })
|
|
||||||
} else if (action instanceof UpdateCategoryBlockAllNotificationsAction) {
|
} else if (action instanceof UpdateCategoryBlockAllNotificationsAction) {
|
||||||
await dispatchUpdateCategoryBlockAllNotifications({ action, cache })
|
return dispatchUpdateCategoryBlockAllNotifications({ action, cache, fromChildSelfLimitAddChildUserId })
|
||||||
} else if (action instanceof UpdateCategoryBlockedTimesAction) {
|
} else if (action instanceof SetParentCategoryAction) {
|
||||||
await dispatchUpdateCategoryBlockedTimes({ action, cache })
|
return dispatchSetParentCategory({ action, cache, fromChildSelfLimitAddChildUserId })
|
||||||
} else if (action instanceof UpdateCategorySortingAction) {
|
|
||||||
await dispatchUpdateCategorySorting({ action, cache })
|
|
||||||
} else if (action instanceof IncrementCategoryExtraTimeAction) {
|
|
||||||
await dispatchIncrementCategoryExtraTime({ action, cache })
|
|
||||||
} else if (action instanceof UpdateCategoryTemporarilyBlockedAction) {
|
} else if (action instanceof UpdateCategoryTemporarilyBlockedAction) {
|
||||||
await dispatchUpdateCategoryTemporarilyBlocked({ action, cache })
|
return dispatchUpdateCategoryTemporarilyBlocked({ action, cache, fromChildSelfLimitAddChildUserId })
|
||||||
} else if (action instanceof DeleteTimeLimitRuleAction) {
|
}
|
||||||
await dispatchDeleteTimeLimitRule({ action, cache })
|
|
||||||
} else if (action instanceof UpdateDeviceNameAction) {
|
if (fromChildSelfLimitAddChildUserId === null) {
|
||||||
await dispatchUpdateDeviceName({ action, cache })
|
if (action instanceof AddUserAction) {
|
||||||
} else if (action instanceof UpdateEnableActivityLevelBlockingAction) {
|
return dispatchAddUser({ action, cache })
|
||||||
await dispatchUpdateEnableActivityLevelBlocking({ action, cache })
|
} else if (action instanceof RemoveCategoryAppsAction) {
|
||||||
} else if (action instanceof UpdateNetworkTimeVerificationAction) {
|
return dispatchRemoveCategoryApps({ action, cache })
|
||||||
await dispatchUpdateNetworkTimeVerification({ action, cache })
|
} else if (action instanceof DeleteCategoryAction) {
|
||||||
} else if (action instanceof UpdateParentNotificationFlagsAction) {
|
return dispatchDeleteCategory({ action, cache })
|
||||||
await dispatchUpdateParentNotificationFlags({ action, cache })
|
} else if (action instanceof UpdateCategoryTitleAction) {
|
||||||
} else if (action instanceof UpdateTimelimitRuleAction) {
|
return dispatchUpdateCategoryTitle({ action, cache })
|
||||||
await dispatchUpdateTimelimitRule({ action, cache })
|
} else if (action instanceof SetCategoryExtraTimeAction) {
|
||||||
} else if (action instanceof RemoveUserAction) {
|
return dispatchSetCategoryExtraTime({ action, cache })
|
||||||
await dispatchRemoveUser({ action, cache, parentUserId })
|
} else if (action instanceof SetCategoryForUnassignedAppsAction) {
|
||||||
} else if (action instanceof RenameChildAction) {
|
return dispatchSetCategoryForUnassignedApps({ action, cache })
|
||||||
await dispatchRenameChild({ action, cache })
|
} else if (action instanceof SetChildPasswordAction) {
|
||||||
} else if (action instanceof ChangeParentPasswordAction) {
|
return dispatchSetChildPassword({ action, cache })
|
||||||
await dispatchChangeParentPassword({ action, cache })
|
} else if (action instanceof SetConsiderRebootManipulationAction) {
|
||||||
} else if (action instanceof IgnoreManipulationAction) {
|
return dispatchSetConsiderRebootManipulation({ action, cache })
|
||||||
await dispatchIgnoreManipulation({ action, cache })
|
} else if (action instanceof SetDeviceDefaultUserAction) {
|
||||||
} else if (action instanceof UpdateCategoryTimeWarningsAction) {
|
return dispatchSetDeviceDefaultUser({ action, cache })
|
||||||
await dispatchUpdateCategoryTimeWarnings({ action, cache })
|
} else if (action instanceof SetDeviceDefaultUserTimeoutAction) {
|
||||||
} else if (action instanceof ResetParentBlockedTimesAction) {
|
return dispatchSetDeviceDefaultUserTimeout({ action, cache })
|
||||||
await dispatchResetParentBlockedTimes({ action, cache })
|
} else if (action instanceof SetDeviceUserAction) {
|
||||||
} else if (action instanceof UpdateParentBlockedTimesAction) {
|
return dispatchSetDeviceUser({ action, cache })
|
||||||
await dispatchUpdateParentBlockedTimes({ action, cache, parentUserId })
|
} else if (action instanceof SetKeepSignedInAction) {
|
||||||
} else if (action instanceof UpdateUserFlagsAction) {
|
return dispatchSetKeepSignedIn({ action, cache, parentUserId })
|
||||||
await dispatchUpdateUserFlagsAction({ action, cache })
|
} else if (action instanceof SetRelaxPrimaryDeviceAction) {
|
||||||
} else if (action instanceof UpdateUserLimitLoginCategory) {
|
return dispatchSetRelaxPrimaryDevice({ action, cache })
|
||||||
await dispatchUpdateUserLimitLoginCategoryAction({ action, cache, parentUserId })
|
} 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 {
|
} else {
|
||||||
throw new Error('unsupported action type')
|
throw new Error('unsupported action type')
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,11 +16,13 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { SetParentCategoryAction } from '../../../../action'
|
import { SetParentCategoryAction } from '../../../../action'
|
||||||
|
import { getCategoryWithParentCategories } from '../../../../util/category'
|
||||||
import { Cache } from '../cache'
|
import { Cache } from '../cache'
|
||||||
|
|
||||||
export async function dispatchSetParentCategory ({ action, cache }: {
|
export async function dispatchSetParentCategory ({ action, cache, fromChildSelfLimitAddChildUserId }: {
|
||||||
action: SetParentCategoryAction
|
action: SetParentCategoryAction
|
||||||
cache: Cache
|
cache: Cache
|
||||||
|
fromChildSelfLimitAddChildUserId: string | null
|
||||||
}) {
|
}) {
|
||||||
const categoryEntry = await cache.database.category.findOne({
|
const categoryEntry = await cache.database.category.findOne({
|
||||||
where: {
|
where: {
|
||||||
@@ -34,6 +36,12 @@ export async function dispatchSetParentCategory ({ action, cache }: {
|
|||||||
throw new Error('tried to set parent category of non existent category')
|
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 !== '') {
|
if (action.parentCategory !== '') {
|
||||||
const categoriesByUserId = (await cache.database.category.findAll({
|
const categoriesByUserId = (await cache.database.category.findAll({
|
||||||
where: {
|
where: {
|
||||||
@@ -74,6 +82,16 @@ export async function dispatchSetParentCategory ({ action, cache }: {
|
|||||||
if (childCategoryIds.has(action.parentCategory) || action.parentCategory === action.categoryId) {
|
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')
|
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({
|
await cache.database.category.update({
|
||||||
|
|||||||
+26
-2
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* server component for the TimeLimit App
|
* 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
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU Affero General Public License as
|
* it under the terms of the GNU Affero General Public License as
|
||||||
@@ -18,10 +18,34 @@
|
|||||||
import { UpdateCategoryBlockAllNotificationsAction } from '../../../../action'
|
import { UpdateCategoryBlockAllNotificationsAction } from '../../../../action'
|
||||||
import { Cache } from '../cache'
|
import { Cache } from '../cache'
|
||||||
|
|
||||||
export async function dispatchUpdateCategoryBlockAllNotifications ({ action, cache }: {
|
export async function dispatchUpdateCategoryBlockAllNotifications ({ action, cache, fromChildSelfLimitAddChildUserId }: {
|
||||||
action: UpdateCategoryBlockAllNotificationsAction
|
action: UpdateCategoryBlockAllNotificationsAction
|
||||||
cache: Cache
|
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({
|
const [affectedRows] = await cache.database.category.update({
|
||||||
blockAllNotifications: action.blocked
|
blockAllNotifications: action.blocked
|
||||||
}, {
|
}, {
|
||||||
|
|||||||
+37
-1
@@ -18,9 +18,10 @@
|
|||||||
import { UpdateCategoryTemporarilyBlockedAction } from '../../../../action'
|
import { UpdateCategoryTemporarilyBlockedAction } from '../../../../action'
|
||||||
import { Cache } from '../cache'
|
import { Cache } from '../cache'
|
||||||
|
|
||||||
export async function dispatchUpdateCategoryTemporarilyBlocked ({ action, cache }: {
|
export async function dispatchUpdateCategoryTemporarilyBlocked ({ action, cache, fromChildSelfLimitAddChildUserId }: {
|
||||||
action: UpdateCategoryTemporarilyBlockedAction
|
action: UpdateCategoryTemporarilyBlockedAction
|
||||||
cache: Cache
|
cache: Cache
|
||||||
|
fromChildSelfLimitAddChildUserId: string | null
|
||||||
}) {
|
}) {
|
||||||
if (action.blocked === true) {
|
if (action.blocked === true) {
|
||||||
if (!cache.hasFullVersion) {
|
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({
|
const [affectedRows] = await cache.database.category.update({
|
||||||
temporarilyBlocked: action.blocked,
|
temporarilyBlocked: action.blocked,
|
||||||
temporarilyBlockedEndTime: action.blocked ? (action.endTime ?? 0) : 0
|
temporarilyBlockedEndTime: action.blocked ? (action.endTime ?? 0) : 0
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import { ClientPushChangesRequest } from '../../../api/schema'
|
|||||||
import { isSerializedAppLogicAction, isSerializedChildAction, isSerializedParentAction } from '../../../api/validator'
|
import { isSerializedAppLogicAction, isSerializedChildAction, isSerializedParentAction } from '../../../api/validator'
|
||||||
import { VisibleConnectedDevicesManager } from '../../../connected-devices'
|
import { VisibleConnectedDevicesManager } from '../../../connected-devices'
|
||||||
import { Database } from '../../../database'
|
import { Database } from '../../../database'
|
||||||
|
import { UserFlags } from '../../../model/userflags'
|
||||||
import { EventHandler } from '../../../monitoring/eventhandler'
|
import { EventHandler } from '../../../monitoring/eventhandler'
|
||||||
import { WebsocketApi } from '../../../websocket'
|
import { WebsocketApi } from '../../../websocket'
|
||||||
import { notifyClientsAboutChanges } from '../../websocket'
|
import { notifyClientsAboutChanges } from '../../websocket'
|
||||||
@@ -106,6 +107,8 @@ export const applyActionsFromDevice = async ({ database, request, websocket, con
|
|||||||
// update the next sequence number
|
// update the next sequence number
|
||||||
nextSequenceNumber = action.sequenceNumber + 1
|
nextSequenceNumber = action.sequenceNumber + 1
|
||||||
|
|
||||||
|
let isChildLimitAdding = false
|
||||||
|
|
||||||
if (action.type === 'parent') {
|
if (action.type === 'parent') {
|
||||||
if (action.integrity === 'device') {
|
if (action.integrity === 'device') {
|
||||||
const deviceEntryUnsafe2 = await cache.database.device.findOne({
|
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
|
// this ensures that the parent exists
|
||||||
await cache.getSecondPasswordHashOfParent(action.userId)
|
await cache.getSecondPasswordHashOfParent(action.userId)
|
||||||
|
} else if (action.integrity === 'childDevice') {
|
||||||
|
// will be checked later
|
||||||
|
isChildLimitAdding = true
|
||||||
} else {
|
} else {
|
||||||
const parentSecondHash = await cache.getSecondPasswordHashOfParent(action.userId)
|
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)
|
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)
|
const parsedAction = parseParentAction(parsedSerializedAction)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await dispatchParentAction({
|
if (isChildLimitAdding) {
|
||||||
action: parsedAction,
|
const deviceEntryUnsafe2 = await cache.database.device.findOne({
|
||||||
cache,
|
attributes: ['currentUserId'],
|
||||||
parentUserId: action.userId,
|
where: {
|
||||||
sourceDeviceId: deviceEntry.deviceId
|
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) {
|
} catch (ex) {
|
||||||
eventHandler.countEvent('applyActionsFromDevice actionWithError:' + parsedSerializedAction.type)
|
eventHandler.countEvent('applyActionsFromDeviceWithError, childAddLimit: ' + isChildLimitAdding + ' action:' + parsedSerializedAction.type)
|
||||||
|
|
||||||
throw ex
|
throw ex
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,5 +17,6 @@
|
|||||||
|
|
||||||
export const UserFlags = {
|
export const UserFlags = {
|
||||||
RESTRICT_VIEWING_TO_PARENTS: 1,
|
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