Compare commits

..
210 changed files with 4472 additions and 2189 deletions
+6 -3
View File
@@ -14,8 +14,8 @@ Use this to get the server status.
## Response ## Response
This returns a JSON object with ``websocketClients`` (of the type number, This returns a JSON object with ``websocketClients`` (of the type number,
the number of clients connected using the websocket) and the map ``counters`` the number of clients connected using the websocket) and the maps ``counters`` and ``maxValues``
which maps values to numbers. You should not make any assumptions about the counter names which map values to numbers. You should not make any assumptions about the key names
and their availability. and their availability.
### example response ### example response
@@ -25,13 +25,16 @@ and their availability.
"websocketClients": 3, "websocketClients": 3,
"counters": { "counters": {
"testCounter": 1 "testCounter": 1
},
"maxValues": {
"testMax": 3
} }
} }
``` ```
## POST /admin/reset-counters ## POST /admin/reset-counters
Use this to reset the counters included in the server status. Use this to reset the counters and maxValues included in the server status.
Although this uses POST, it does not take any request body Although this uses POST, it does not take any request body
@@ -7,37 +7,7 @@
"actions": { "actions": {
"type": "array", "type": "array",
"items": { "items": {
"type": "object", "$ref": "#/definitions/ClientPushChangesRequestAction"
"properties": {
"encodedAction": {
"type": "string"
},
"sequenceNumber": {
"type": "number"
},
"integrity": {
"type": "string"
},
"type": {
"enum": [
"appLogic",
"child",
"parent"
],
"type": "string"
},
"userId": {
"type": "string"
}
},
"additionalProperties": false,
"required": [
"encodedAction",
"integrity",
"sequenceNumber",
"type",
"userId"
]
} }
} }
}, },
@@ -46,6 +16,42 @@
"actions", "actions",
"deviceAuthToken" "deviceAuthToken"
], ],
"definitions": {
"ClientPushChangesRequestAction": {
"type": "object",
"properties": {
"encodedAction": {
"type": "string"
},
"sequenceNumber": {
"type": "number"
},
"integrity": {
"type": "string"
},
"type": {
"enum": [
"appLogic",
"child",
"parent"
],
"type": "string"
},
"userId": {
"type": "string"
}
},
"additionalProperties": false,
"required": [
"encodedAction",
"integrity",
"sequenceNumber",
"type",
"userId"
],
"title": "ClientPushChangesRequestAction"
}
},
"$schema": "http://json-schema.org/draft-07/schema#", "$schema": "http://json-schema.org/draft-07/schema#",
"title": "ClientPushChangesRequest", "title": "ClientPushChangesRequest",
"$id": "https://timelimit.io/ClientPushChangesRequest" "$id": "https://timelimit.io/ClientPushChangesRequest"
+48 -47
View File
@@ -33,26 +33,28 @@
- [CategoryDataStatus](./clientpullchangesrequest-definitions-categorydatastatus.md) `https://timelimit.io/ClientPullChangesRequest#/definitions/CategoryDataStatus` - [CategoryDataStatus](./clientpullchangesrequest-definitions-categorydatastatus.md) `https://timelimit.io/ClientPullChangesRequest#/definitions/CategoryDataStatus`
- [ClientDataStatus](./clientpullchangesrequest-properties-clientdatastatus.md) `https://timelimit.io/ClientPullChangesRequest#/properties/status` - [ClientDataStatus](./clientpullchangesrequest-properties-clientdatastatus.md) `https://timelimit.io/ClientPullChangesRequest#/properties/status`
- [ClientDataStatus](./clientpullchangesrequest-definitions-clientdatastatus.md) `https://timelimit.io/ClientPullChangesRequest#/definitions/ClientDataStatus` - [ClientDataStatus](./clientpullchangesrequest-definitions-clientdatastatus.md) `https://timelimit.io/ClientPullChangesRequest#/definitions/ClientDataStatus`
- [NewDeviceInfo](./registerchilddevicerequest-definitions-newdeviceinfo.md) `https://timelimit.io/RegisterChildDeviceRequest#/definitions/NewDeviceInfo` - [ClientPushChangesRequestAction](./clientpushchangesrequest-properties-actions-clientpushchangesrequestaction.md) `https://timelimit.io/ClientPushChangesRequest#/properties/actions/items`
- [NewDeviceInfo](./registerchilddevicerequest-properties-newdeviceinfo.md) `https://timelimit.io/RegisterChildDeviceRequest#/properties/childDevice` - [ClientPushChangesRequestAction](./clientpushchangesrequest-definitions-clientpushchangesrequestaction.md) `https://timelimit.io/ClientPushChangesRequest#/definitions/ClientPushChangesRequestAction`
- [NewDeviceInfo](./signintofamilyrequest-definitions-newdeviceinfo.md) `https://timelimit.io/SignIntoFamilyRequest#/definitions/NewDeviceInfo` - [NewDeviceInfo](./signintofamilyrequest-definitions-newdeviceinfo.md) `https://timelimit.io/SignIntoFamilyRequest#/definitions/NewDeviceInfo`
- [NewDeviceInfo](./signintofamilyrequest-properties-newdeviceinfo.md) `https://timelimit.io/SignIntoFamilyRequest#/properties/parentDevice` - [NewDeviceInfo](./signintofamilyrequest-properties-newdeviceinfo.md) `https://timelimit.io/SignIntoFamilyRequest#/properties/parentDevice`
- [NewDeviceInfo](./registerchilddevicerequest-definitions-newdeviceinfo.md) `https://timelimit.io/RegisterChildDeviceRequest#/definitions/NewDeviceInfo`
- [NewDeviceInfo](./registerchilddevicerequest-properties-newdeviceinfo.md) `https://timelimit.io/RegisterChildDeviceRequest#/properties/childDevice`
- [NewDeviceInfo](./createfamilybymailtokenrequest-definitions-newdeviceinfo.md) `https://timelimit.io/CreateFamilyByMailTokenRequest#/definitions/NewDeviceInfo` - [NewDeviceInfo](./createfamilybymailtokenrequest-definitions-newdeviceinfo.md) `https://timelimit.io/CreateFamilyByMailTokenRequest#/definitions/NewDeviceInfo`
- [NewDeviceInfo](./createfamilybymailtokenrequest-properties-newdeviceinfo.md) `https://timelimit.io/CreateFamilyByMailTokenRequest#/properties/parentDevice` - [NewDeviceInfo](./createfamilybymailtokenrequest-properties-newdeviceinfo.md) `https://timelimit.io/CreateFamilyByMailTokenRequest#/properties/parentDevice`
- [ParentPassword](./serializedparentaction-definitions-serializedadduseraction-properties-parentpassword.md) `https://timelimit.io/SerializedParentAction#/definitions/SerializedAddUserAction/properties/password` - [ParentPassword](./serializedparentaction-definitions-serializedadduseraction-properties-parentpassword.md) `https://timelimit.io/SerializedParentAction#/definitions/SerializedAddUserAction/properties/password`
- [ParentPassword](./createfamilybymailtokenrequest-properties-parentpassword.md) `https://timelimit.io/CreateFamilyByMailTokenRequest#/properties/parentPassword` - [ParentPassword](./createfamilybymailtokenrequest-definitions-parentpassword.md) `https://timelimit.io/CreateFamilyByMailTokenRequest#/definitions/ParentPassword`
- [ParentPassword](./recoverparentpasswordrequest-definitions-parentpassword.md) `https://timelimit.io/RecoverParentPasswordRequest#/definitions/ParentPassword` - [ParentPassword](./serializedchildaction-definitions-serializedchildchangepasswordaction-properties-parentpassword.md) `https://timelimit.io/SerializedChildAction#/definitions/SerializedChildChangePasswordAction/properties/password`
- [ParentPassword](./serializedparentaction-definitions-serializedsetchildpasswordaction-properties-parentpassword.md) `https://timelimit.io/SerializedParentAction#/definitions/SerializedSetChildPasswordAction/properties/newPassword` - [ParentPassword](./serializedparentaction-definitions-serializedsetchildpasswordaction-properties-parentpassword.md) `https://timelimit.io/SerializedParentAction#/definitions/SerializedSetChildPasswordAction/properties/newPassword`
- [ParentPassword](./serializedchildaction-definitions-serializedchildchangepasswordaction-properties-parentpassword.md) `https://timelimit.io/SerializedChildAction#/definitions/SerializedChildChangePasswordAction/properties/password` - [ParentPassword](./serializedchildaction-definitions-serializedchildchangepasswordaction-properties-parentpassword.md) `https://timelimit.io/SerializedChildAction#/definitions/SerializedChildChangePasswordAction/properties/password`
- [ParentPassword](./serializedparentaction-definitions-parentpassword.md) `https://timelimit.io/SerializedParentAction#/definitions/ParentPassword` - [ParentPassword](./serializedparentaction-definitions-parentpassword.md) `https://timelimit.io/SerializedParentAction#/definitions/ParentPassword`
- [ParentPassword](./recoverparentpasswordrequest-properties-parentpassword.md) `https://timelimit.io/RecoverParentPasswordRequest#/properties/password` - [ParentPassword](./recoverparentpasswordrequest-definitions-parentpassword.md) `https://timelimit.io/RecoverParentPasswordRequest#/definitions/ParentPassword`
- [ParentPassword](./createfamilybymailtokenrequest-definitions-parentpassword.md) `https://timelimit.io/CreateFamilyByMailTokenRequest#/definitions/ParentPassword` - [ParentPassword](./createfamilybymailtokenrequest-properties-parentpassword.md) `https://timelimit.io/CreateFamilyByMailTokenRequest#/properties/parentPassword`
- [ParentPassword](./serializedchildaction-definitions-serializedchildchangepasswordaction-properties-parentpassword.md) `https://timelimit.io/SerializedChildAction#/definitions/SerializedChildChangePasswordAction/properties/password`
- [ParentPassword](./serializedchildaction-definitions-parentpassword.md) `https://timelimit.io/SerializedChildAction#/definitions/ParentPassword` - [ParentPassword](./serializedchildaction-definitions-parentpassword.md) `https://timelimit.io/SerializedChildAction#/definitions/ParentPassword`
- [ParentPassword](./recoverparentpasswordrequest-properties-parentpassword.md) `https://timelimit.io/RecoverParentPasswordRequest#/properties/password`
- [ParentPassword](./serializedparentaction-definitions-serializedsetchildpasswordaction-properties-parentpassword.md) `https://timelimit.io/SerializedParentAction#/definitions/SerializedSetChildPasswordAction/properties/newPassword` - [ParentPassword](./serializedparentaction-definitions-serializedsetchildpasswordaction-properties-parentpassword.md) `https://timelimit.io/SerializedParentAction#/definitions/SerializedSetChildPasswordAction/properties/newPassword`
- [ParentPassword](./serializedparentaction-definitions-serializedadduseraction-properties-parentpassword.md) `https://timelimit.io/SerializedParentAction#/definitions/SerializedAddUserAction/properties/password` - [ParentPassword](./serializedparentaction-definitions-serializedadduseraction-properties-parentpassword.md) `https://timelimit.io/SerializedParentAction#/definitions/SerializedAddUserAction/properties/password`
- [SerialiezdTriedDisablingDeviceAdminAction](./serializedapplogicaction-anyof-serialiezdtrieddisablingdeviceadminaction.md) `https://timelimit.io/SerializedAppLogicAction#/anyOf/6`
- [SerialiezdTriedDisablingDeviceAdminAction](./serializedapplogicaction-definitions-serialiezdtrieddisablingdeviceadminaction.md) `https://timelimit.io/SerializedAppLogicAction#/definitions/SerialiezdTriedDisablingDeviceAdminAction` - [SerialiezdTriedDisablingDeviceAdminAction](./serializedapplogicaction-definitions-serialiezdtrieddisablingdeviceadminaction.md) `https://timelimit.io/SerializedAppLogicAction#/definitions/SerialiezdTriedDisablingDeviceAdminAction`
- [SerialiezdTriedDisablingDeviceAdminAction](./serializedapplogicaction-anyof-serialiezdtrieddisablingdeviceadminaction.md) `https://timelimit.io/SerializedAppLogicAction#/anyOf/6`
- [SerialiizedUpdateNetworkTimeVerificationAction](./serializedparentaction-definitions-serialiizedupdatenetworktimeverificationaction.md) `https://timelimit.io/SerializedParentAction#/definitions/SerialiizedUpdateNetworkTimeVerificationAction` - [SerialiizedUpdateNetworkTimeVerificationAction](./serializedparentaction-definitions-serialiizedupdatenetworktimeverificationaction.md) `https://timelimit.io/SerializedParentAction#/definitions/SerialiizedUpdateNetworkTimeVerificationAction`
- [SerialiizedUpdateNetworkTimeVerificationAction](./serializedparentaction-anyof-serialiizedupdatenetworktimeverificationaction.md) `https://timelimit.io/SerializedParentAction#/anyOf/37` - [SerialiizedUpdateNetworkTimeVerificationAction](./serializedparentaction-anyof-serialiizedupdatenetworktimeverificationaction.md) `https://timelimit.io/SerializedParentAction#/anyOf/37`
- [SerializeResetCategoryNetworkIdsAction](./serializedparentaction-anyof-serializeresetcategorynetworkidsaction.md) `https://timelimit.io/SerializedParentAction#/anyOf/13` - [SerializeResetCategoryNetworkIdsAction](./serializedparentaction-anyof-serializeresetcategorynetworkidsaction.md) `https://timelimit.io/SerializedParentAction#/anyOf/13`
@@ -63,18 +65,18 @@
- [SerializedAddCategoryNetworkIdAction](./serializedparentaction-definitions-serializedaddcategorynetworkidaction.md) `https://timelimit.io/SerializedParentAction#/definitions/SerializedAddCategoryNetworkIdAction` - [SerializedAddCategoryNetworkIdAction](./serializedparentaction-definitions-serializedaddcategorynetworkidaction.md) `https://timelimit.io/SerializedParentAction#/definitions/SerializedAddCategoryNetworkIdAction`
- [SerializedAddInstalledAppsAction](./serializedapplogicaction-definitions-serializedaddinstalledappsaction.md) `https://timelimit.io/SerializedAppLogicAction#/definitions/SerializedAddInstalledAppsAction` - [SerializedAddInstalledAppsAction](./serializedapplogicaction-definitions-serializedaddinstalledappsaction.md) `https://timelimit.io/SerializedAppLogicAction#/definitions/SerializedAddInstalledAppsAction`
- [SerializedAddInstalledAppsAction](./serializedapplogicaction-anyof-serializedaddinstalledappsaction.md) `https://timelimit.io/SerializedAppLogicAction#/anyOf/0` - [SerializedAddInstalledAppsAction](./serializedapplogicaction-anyof-serializedaddinstalledappsaction.md) `https://timelimit.io/SerializedAppLogicAction#/anyOf/0`
- [SerializedAddUsedTimeAction](./serializedapplogicaction-definitions-serializedaddusedtimeaction.md) `https://timelimit.io/SerializedAppLogicAction#/definitions/SerializedAddUsedTimeAction`
- [SerializedAddUsedTimeAction](./serializedapplogicaction-anyof-serializedaddusedtimeaction.md) `https://timelimit.io/SerializedAppLogicAction#/anyOf/1` - [SerializedAddUsedTimeAction](./serializedapplogicaction-anyof-serializedaddusedtimeaction.md) `https://timelimit.io/SerializedAppLogicAction#/anyOf/1`
- [SerializedAddUsedTimeActionVersion2](./serializedapplogicaction-definitions-serializedaddusedtimeactionversion2.md) `https://timelimit.io/SerializedAppLogicAction#/definitions/SerializedAddUsedTimeActionVersion2` - [SerializedAddUsedTimeAction](./serializedapplogicaction-definitions-serializedaddusedtimeaction.md) `https://timelimit.io/SerializedAppLogicAction#/definitions/SerializedAddUsedTimeAction`
- [SerializedAddUsedTimeActionVersion2](./serializedapplogicaction-anyof-serializedaddusedtimeactionversion2.md) `https://timelimit.io/SerializedAppLogicAction#/anyOf/2` - [SerializedAddUsedTimeActionVersion2](./serializedapplogicaction-anyof-serializedaddusedtimeactionversion2.md) `https://timelimit.io/SerializedAppLogicAction#/anyOf/2`
- [SerializedAddUserAction](./serializedparentaction-definitions-serializedadduseraction.md) `https://timelimit.io/SerializedParentAction#/definitions/SerializedAddUserAction` - [SerializedAddUsedTimeActionVersion2](./serializedapplogicaction-definitions-serializedaddusedtimeactionversion2.md) `https://timelimit.io/SerializedAppLogicAction#/definitions/SerializedAddUsedTimeActionVersion2`
- [SerializedAddUserAction](./serializedparentaction-anyof-serializedadduseraction.md) `https://timelimit.io/SerializedParentAction#/anyOf/2` - [SerializedAddUserAction](./serializedparentaction-anyof-serializedadduseraction.md) `https://timelimit.io/SerializedParentAction#/anyOf/2`
- [SerializedAppActivityItem](./serverdatastatus-definitions-serverinstalledappsdata-properties-activities-serializedappactivityitem.md) `https://timelimit.io/ServerDataStatus#/definitions/ServerInstalledAppsData/properties/activities/items` - [SerializedAddUserAction](./serializedparentaction-definitions-serializedadduseraction.md) `https://timelimit.io/SerializedParentAction#/definitions/SerializedAddUserAction`
- [SerializedAppActivityItem](./serializedapplogicaction-definitions-serializedappactivityitem.md) `https://timelimit.io/SerializedAppLogicAction#/definitions/SerializedAppActivityItem`
- [SerializedAppActivityItem](./serializedapplogicaction-definitions-serializedupdateappactivitiesaction-properties-updatedoradded-serializedappactivityitem.md) `https://timelimit.io/SerializedAppLogicAction#/definitions/SerializedUpdateAppActivitiesAction/properties/updatedOrAdded/items`
- [SerializedAppActivityItem](./serverdatastatus-definitions-serverinstalledappsdata-properties-activities-serializedappactivityitem.md) `https://timelimit.io/ServerDataStatus#/definitions/ServerInstalledAppsData/properties/activities/items` - [SerializedAppActivityItem](./serverdatastatus-definitions-serverinstalledappsdata-properties-activities-serializedappactivityitem.md) `https://timelimit.io/ServerDataStatus#/definitions/ServerInstalledAppsData/properties/activities/items`
- [SerializedAppActivityItem](./serverdatastatus-definitions-serializedappactivityitem.md) `https://timelimit.io/ServerDataStatus#/definitions/SerializedAppActivityItem` - [SerializedAppActivityItem](./serverdatastatus-definitions-serializedappactivityitem.md) `https://timelimit.io/ServerDataStatus#/definitions/SerializedAppActivityItem`
- [SerializedAppActivityItem](./serializedapplogicaction-definitions-serializedupdateappactivitiesaction-properties-updatedoradded-serializedappactivityitem.md) `https://timelimit.io/SerializedAppLogicAction#/definitions/SerializedUpdateAppActivitiesAction/properties/updatedOrAdded/items` - [SerializedAppActivityItem](./serializedapplogicaction-definitions-serializedupdateappactivitiesaction-properties-updatedoradded-serializedappactivityitem.md) `https://timelimit.io/SerializedAppLogicAction#/definitions/SerializedUpdateAppActivitiesAction/properties/updatedOrAdded/items`
- [SerializedAppActivityItem](./serializedapplogicaction-definitions-serializedappactivityitem.md) `https://timelimit.io/SerializedAppLogicAction#/definitions/SerializedAppActivityItem`
- [SerializedAppActivityItem](./serializedapplogicaction-definitions-serializedupdateappactivitiesaction-properties-updatedoradded-serializedappactivityitem.md) `https://timelimit.io/SerializedAppLogicAction#/definitions/SerializedUpdateAppActivitiesAction/properties/updatedOrAdded/items`
- [SerializedAppActivityItem](./serverdatastatus-definitions-serverinstalledappsdata-properties-activities-serializedappactivityitem.md) `https://timelimit.io/ServerDataStatus#/definitions/ServerInstalledAppsData/properties/activities/items`
- [SerializedChangeParentPasswordAction](./serializedparentaction-definitions-serializedchangeparentpasswordaction.md) `https://timelimit.io/SerializedParentAction#/definitions/SerializedChangeParentPasswordAction` - [SerializedChangeParentPasswordAction](./serializedparentaction-definitions-serializedchangeparentpasswordaction.md) `https://timelimit.io/SerializedParentAction#/definitions/SerializedChangeParentPasswordAction`
- [SerializedChangeParentPasswordAction](./serializedparentaction-anyof-serializedchangeparentpasswordaction.md) `https://timelimit.io/SerializedParentAction#/anyOf/3` - [SerializedChangeParentPasswordAction](./serializedparentaction-anyof-serializedchangeparentpasswordaction.md) `https://timelimit.io/SerializedParentAction#/anyOf/3`
- [SerializedChildChangePasswordAction](./serializedchildaction-definitions-serializedchildchangepasswordaction.md) `https://timelimit.io/SerializedChildAction#/definitions/SerializedChildChangePasswordAction` - [SerializedChildChangePasswordAction](./serializedchildaction-definitions-serializedchildchangepasswordaction.md) `https://timelimit.io/SerializedChildAction#/definitions/SerializedChildChangePasswordAction`
@@ -83,26 +85,26 @@
- [SerializedChildSignInAction](./serializedchildaction-anyof-serializedchildsigninaction.md) `https://timelimit.io/SerializedChildAction#/anyOf/1` - [SerializedChildSignInAction](./serializedchildaction-anyof-serializedchildsigninaction.md) `https://timelimit.io/SerializedChildAction#/anyOf/1`
- [SerializedCreateCategoryAction](./serializedparentaction-definitions-serializedcreatecategoryaction.md) `https://timelimit.io/SerializedParentAction#/definitions/SerializedCreateCategoryAction` - [SerializedCreateCategoryAction](./serializedparentaction-definitions-serializedcreatecategoryaction.md) `https://timelimit.io/SerializedParentAction#/definitions/SerializedCreateCategoryAction`
- [SerializedCreateCategoryAction](./serializedparentaction-anyof-serializedcreatecategoryaction.md) `https://timelimit.io/SerializedParentAction#/anyOf/4` - [SerializedCreateCategoryAction](./serializedparentaction-anyof-serializedcreatecategoryaction.md) `https://timelimit.io/SerializedParentAction#/anyOf/4`
- [SerializedCreateTimelimtRuleAction](./serializedparentaction-definitions-serializedcreatetimelimtruleaction.md) `https://timelimit.io/SerializedParentAction#/definitions/SerializedCreateTimelimtRuleAction`
- [SerializedCreateTimelimtRuleAction](./serializedparentaction-anyof-serializedcreatetimelimtruleaction.md) `https://timelimit.io/SerializedParentAction#/anyOf/5` - [SerializedCreateTimelimtRuleAction](./serializedparentaction-anyof-serializedcreatetimelimtruleaction.md) `https://timelimit.io/SerializedParentAction#/anyOf/5`
- [SerializedDeleteCategoryAction](./serializedparentaction-definitions-serializeddeletecategoryaction.md) `https://timelimit.io/SerializedParentAction#/definitions/SerializedDeleteCategoryAction` - [SerializedCreateTimelimtRuleAction](./serializedparentaction-definitions-serializedcreatetimelimtruleaction.md) `https://timelimit.io/SerializedParentAction#/definitions/SerializedCreateTimelimtRuleAction`
- [SerializedDeleteCategoryAction](./serializedparentaction-anyof-serializeddeletecategoryaction.md) `https://timelimit.io/SerializedParentAction#/anyOf/6` - [SerializedDeleteCategoryAction](./serializedparentaction-anyof-serializeddeletecategoryaction.md) `https://timelimit.io/SerializedParentAction#/anyOf/6`
- [SerializedDeleteCategoryAction](./serializedparentaction-definitions-serializeddeletecategoryaction.md) `https://timelimit.io/SerializedParentAction#/definitions/SerializedDeleteCategoryAction`
- [SerializedDeleteTimeLimitRuleAction](./serializedparentaction-anyof-serializeddeletetimelimitruleaction.md) `https://timelimit.io/SerializedParentAction#/anyOf/7` - [SerializedDeleteTimeLimitRuleAction](./serializedparentaction-anyof-serializeddeletetimelimitruleaction.md) `https://timelimit.io/SerializedParentAction#/anyOf/7`
- [SerializedDeleteTimeLimitRuleAction](./serializedparentaction-definitions-serializeddeletetimelimitruleaction.md) `https://timelimit.io/SerializedParentAction#/definitions/SerializedDeleteTimeLimitRuleAction` - [SerializedDeleteTimeLimitRuleAction](./serializedparentaction-definitions-serializeddeletetimelimitruleaction.md) `https://timelimit.io/SerializedParentAction#/definitions/SerializedDeleteTimeLimitRuleAction`
- [SerializedForceSyncAction](./serializedapplogicaction-definitions-serializedforcesyncaction.md) `https://timelimit.io/SerializedAppLogicAction#/definitions/SerializedForceSyncAction` - [SerializedForceSyncAction](./serializedapplogicaction-definitions-serializedforcesyncaction.md) `https://timelimit.io/SerializedAppLogicAction#/definitions/SerializedForceSyncAction`
- [SerializedForceSyncAction](./serializedapplogicaction-anyof-serializedforcesyncaction.md) `https://timelimit.io/SerializedAppLogicAction#/anyOf/3` - [SerializedForceSyncAction](./serializedapplogicaction-anyof-serializedforcesyncaction.md) `https://timelimit.io/SerializedAppLogicAction#/anyOf/3`
- [SerializedIgnoreManipulationAction](./serializedparentaction-anyof-serializedignoremanipulationaction.md) `https://timelimit.io/SerializedParentAction#/anyOf/8`
- [SerializedIgnoreManipulationAction](./serializedparentaction-definitions-serializedignoremanipulationaction.md) `https://timelimit.io/SerializedParentAction#/definitions/SerializedIgnoreManipulationAction` - [SerializedIgnoreManipulationAction](./serializedparentaction-definitions-serializedignoremanipulationaction.md) `https://timelimit.io/SerializedParentAction#/definitions/SerializedIgnoreManipulationAction`
- [SerializedIncrementCategoryExtraTimeAction](./serializedparentaction-anyof-serializedincrementcategoryextratimeaction.md) `https://timelimit.io/SerializedParentAction#/anyOf/9` - [SerializedIgnoreManipulationAction](./serializedparentaction-anyof-serializedignoremanipulationaction.md) `https://timelimit.io/SerializedParentAction#/anyOf/8`
- [SerializedIncrementCategoryExtraTimeAction](./serializedparentaction-definitions-serializedincrementcategoryextratimeaction.md) `https://timelimit.io/SerializedParentAction#/definitions/SerializedIncrementCategoryExtraTimeAction` - [SerializedIncrementCategoryExtraTimeAction](./serializedparentaction-definitions-serializedincrementcategoryextratimeaction.md) `https://timelimit.io/SerializedParentAction#/definitions/SerializedIncrementCategoryExtraTimeAction`
- [SerializedInstalledApp](./serializedapplogicaction-definitions-serializedaddinstalledappsaction-properties-apps-serializedinstalledapp.md) `https://timelimit.io/SerializedAppLogicAction#/definitions/SerializedAddInstalledAppsAction/properties/apps/items` - [SerializedIncrementCategoryExtraTimeAction](./serializedparentaction-anyof-serializedincrementcategoryextratimeaction.md) `https://timelimit.io/SerializedParentAction#/anyOf/9`
- [SerializedInstalledApp](./serverdatastatus-definitions-serverinstalledappsdata-properties-apps-serializedinstalledapp.md) `https://timelimit.io/ServerDataStatus#/definitions/ServerInstalledAppsData/properties/apps/items`
- [SerializedInstalledApp](./serializedapplogicaction-definitions-serializedinstalledapp.md) `https://timelimit.io/SerializedAppLogicAction#/definitions/SerializedInstalledApp`
- [SerializedInstalledApp](./serializedapplogicaction-definitions-serializedaddinstalledappsaction-properties-apps-serializedinstalledapp.md) `https://timelimit.io/SerializedAppLogicAction#/definitions/SerializedAddInstalledAppsAction/properties/apps/items`
- [SerializedInstalledApp](./serverdatastatus-definitions-serverinstalledappsdata-properties-apps-serializedinstalledapp.md) `https://timelimit.io/ServerDataStatus#/definitions/ServerInstalledAppsData/properties/apps/items`
- [SerializedInstalledApp](./serverdatastatus-definitions-serializedinstalledapp.md) `https://timelimit.io/ServerDataStatus#/definitions/SerializedInstalledApp` - [SerializedInstalledApp](./serverdatastatus-definitions-serializedinstalledapp.md) `https://timelimit.io/ServerDataStatus#/definitions/SerializedInstalledApp`
- [SerializedRemoveCategoryAppsAction](./serializedparentaction-definitions-serializedremovecategoryappsaction.md) `https://timelimit.io/SerializedParentAction#/definitions/SerializedRemoveCategoryAppsAction` - [SerializedInstalledApp](./serverdatastatus-definitions-serverinstalledappsdata-properties-apps-serializedinstalledapp.md) `https://timelimit.io/ServerDataStatus#/definitions/ServerInstalledAppsData/properties/apps/items`
- [SerializedInstalledApp](./serializedapplogicaction-definitions-serializedaddinstalledappsaction-properties-apps-serializedinstalledapp.md) `https://timelimit.io/SerializedAppLogicAction#/definitions/SerializedAddInstalledAppsAction/properties/apps/items`
- [SerializedInstalledApp](./serializedapplogicaction-definitions-serializedinstalledapp.md) `https://timelimit.io/SerializedAppLogicAction#/definitions/SerializedInstalledApp`
- [SerializedInstalledApp](./serverdatastatus-definitions-serverinstalledappsdata-properties-apps-serializedinstalledapp.md) `https://timelimit.io/ServerDataStatus#/definitions/ServerInstalledAppsData/properties/apps/items`
- [SerializedInstalledApp](./serializedapplogicaction-definitions-serializedaddinstalledappsaction-properties-apps-serializedinstalledapp.md) `https://timelimit.io/SerializedAppLogicAction#/definitions/SerializedAddInstalledAppsAction/properties/apps/items`
- [SerializedRemoveCategoryAppsAction](./serializedparentaction-anyof-serializedremovecategoryappsaction.md) `https://timelimit.io/SerializedParentAction#/anyOf/10` - [SerializedRemoveCategoryAppsAction](./serializedparentaction-anyof-serializedremovecategoryappsaction.md) `https://timelimit.io/SerializedParentAction#/anyOf/10`
- [SerializedRemoveCategoryAppsAction](./serializedparentaction-definitions-serializedremovecategoryappsaction.md) `https://timelimit.io/SerializedParentAction#/definitions/SerializedRemoveCategoryAppsAction`
- [SerializedRemoveInstalledAppsAction](./serializedapplogicaction-definitions-serializedremoveinstalledappsaction.md) `https://timelimit.io/SerializedAppLogicAction#/definitions/SerializedRemoveInstalledAppsAction` - [SerializedRemoveInstalledAppsAction](./serializedapplogicaction-definitions-serializedremoveinstalledappsaction.md) `https://timelimit.io/SerializedAppLogicAction#/definitions/SerializedRemoveInstalledAppsAction`
- [SerializedRemoveInstalledAppsAction](./serializedapplogicaction-anyof-serializedremoveinstalledappsaction.md) `https://timelimit.io/SerializedAppLogicAction#/anyOf/4` - [SerializedRemoveInstalledAppsAction](./serializedapplogicaction-anyof-serializedremoveinstalledappsaction.md) `https://timelimit.io/SerializedAppLogicAction#/anyOf/4`
- [SerializedRemoveUserAction](./serializedparentaction-definitions-serializedremoveuseraction.md) `https://timelimit.io/SerializedParentAction#/definitions/SerializedRemoveUserAction` - [SerializedRemoveUserAction](./serializedparentaction-definitions-serializedremoveuseraction.md) `https://timelimit.io/SerializedParentAction#/definitions/SerializedRemoveUserAction`
@@ -111,26 +113,26 @@
- [SerializedRenameChildAction](./serializedparentaction-anyof-serializedrenamechildaction.md) `https://timelimit.io/SerializedParentAction#/anyOf/12` - [SerializedRenameChildAction](./serializedparentaction-anyof-serializedrenamechildaction.md) `https://timelimit.io/SerializedParentAction#/anyOf/12`
- [SerializedResetParentBlockedTimesAction](./serializedparentaction-anyof-serializedresetparentblockedtimesaction.md) `https://timelimit.io/SerializedParentAction#/anyOf/14` - [SerializedResetParentBlockedTimesAction](./serializedparentaction-anyof-serializedresetparentblockedtimesaction.md) `https://timelimit.io/SerializedParentAction#/anyOf/14`
- [SerializedResetParentBlockedTimesAction](./serializedparentaction-definitions-serializedresetparentblockedtimesaction.md) `https://timelimit.io/SerializedParentAction#/definitions/SerializedResetParentBlockedTimesAction` - [SerializedResetParentBlockedTimesAction](./serializedparentaction-definitions-serializedresetparentblockedtimesaction.md) `https://timelimit.io/SerializedParentAction#/definitions/SerializedResetParentBlockedTimesAction`
- [SerializedSetCategoryExtraTimeAction](./serializedparentaction-anyof-serializedsetcategoryextratimeaction.md) `https://timelimit.io/SerializedParentAction#/anyOf/15`
- [SerializedSetCategoryExtraTimeAction](./serializedparentaction-definitions-serializedsetcategoryextratimeaction.md) `https://timelimit.io/SerializedParentAction#/definitions/SerializedSetCategoryExtraTimeAction` - [SerializedSetCategoryExtraTimeAction](./serializedparentaction-definitions-serializedsetcategoryextratimeaction.md) `https://timelimit.io/SerializedParentAction#/definitions/SerializedSetCategoryExtraTimeAction`
- [SerializedSetCategoryForUnassignedAppsAction](./serializedparentaction-anyof-serializedsetcategoryforunassignedappsaction.md) `https://timelimit.io/SerializedParentAction#/anyOf/16` - [SerializedSetCategoryExtraTimeAction](./serializedparentaction-anyof-serializedsetcategoryextratimeaction.md) `https://timelimit.io/SerializedParentAction#/anyOf/15`
- [SerializedSetCategoryForUnassignedAppsAction](./serializedparentaction-definitions-serializedsetcategoryforunassignedappsaction.md) `https://timelimit.io/SerializedParentAction#/definitions/SerializedSetCategoryForUnassignedAppsAction` - [SerializedSetCategoryForUnassignedAppsAction](./serializedparentaction-definitions-serializedsetcategoryforunassignedappsaction.md) `https://timelimit.io/SerializedParentAction#/definitions/SerializedSetCategoryForUnassignedAppsAction`
- [SerializedSetCategoryForUnassignedAppsAction](./serializedparentaction-anyof-serializedsetcategoryforunassignedappsaction.md) `https://timelimit.io/SerializedParentAction#/anyOf/16`
- [SerializedSetChildPasswordAction](./serializedparentaction-definitions-serializedsetchildpasswordaction.md) `https://timelimit.io/SerializedParentAction#/definitions/SerializedSetChildPasswordAction` - [SerializedSetChildPasswordAction](./serializedparentaction-definitions-serializedsetchildpasswordaction.md) `https://timelimit.io/SerializedParentAction#/definitions/SerializedSetChildPasswordAction`
- [SerializedSetChildPasswordAction](./serializedparentaction-anyof-serializedsetchildpasswordaction.md) `https://timelimit.io/SerializedParentAction#/anyOf/17` - [SerializedSetChildPasswordAction](./serializedparentaction-anyof-serializedsetchildpasswordaction.md) `https://timelimit.io/SerializedParentAction#/anyOf/17`
- [SerializedSetConsiderRebootManipulationAction](./serializedparentaction-definitions-serializedsetconsiderrebootmanipulationaction.md) `https://timelimit.io/SerializedParentAction#/definitions/SerializedSetConsiderRebootManipulationAction`
- [SerializedSetConsiderRebootManipulationAction](./serializedparentaction-anyof-serializedsetconsiderrebootmanipulationaction.md) `https://timelimit.io/SerializedParentAction#/anyOf/18` - [SerializedSetConsiderRebootManipulationAction](./serializedparentaction-anyof-serializedsetconsiderrebootmanipulationaction.md) `https://timelimit.io/SerializedParentAction#/anyOf/18`
- [SerializedSetConsiderRebootManipulationAction](./serializedparentaction-definitions-serializedsetconsiderrebootmanipulationaction.md) `https://timelimit.io/SerializedParentAction#/definitions/SerializedSetConsiderRebootManipulationAction`
- [SerializedSetDeviceDefaultUserAction](./serializedparentaction-definitions-serializedsetdevicedefaultuseraction.md) `https://timelimit.io/SerializedParentAction#/definitions/SerializedSetDeviceDefaultUserAction` - [SerializedSetDeviceDefaultUserAction](./serializedparentaction-definitions-serializedsetdevicedefaultuseraction.md) `https://timelimit.io/SerializedParentAction#/definitions/SerializedSetDeviceDefaultUserAction`
- [SerializedSetDeviceDefaultUserAction](./serializedparentaction-anyof-serializedsetdevicedefaultuseraction.md) `https://timelimit.io/SerializedParentAction#/anyOf/19` - [SerializedSetDeviceDefaultUserAction](./serializedparentaction-anyof-serializedsetdevicedefaultuseraction.md) `https://timelimit.io/SerializedParentAction#/anyOf/19`
- [SerializedSetDeviceDefaultUserTimeoutAction](./serializedparentaction-definitions-serializedsetdevicedefaultusertimeoutaction.md) `https://timelimit.io/SerializedParentAction#/definitions/SerializedSetDeviceDefaultUserTimeoutAction`
- [SerializedSetDeviceDefaultUserTimeoutAction](./serializedparentaction-anyof-serializedsetdevicedefaultusertimeoutaction.md) `https://timelimit.io/SerializedParentAction#/anyOf/20` - [SerializedSetDeviceDefaultUserTimeoutAction](./serializedparentaction-anyof-serializedsetdevicedefaultusertimeoutaction.md) `https://timelimit.io/SerializedParentAction#/anyOf/20`
- [SerializedSetDeviceUserAction](./serializedparentaction-anyof-serializedsetdeviceuseraction.md) `https://timelimit.io/SerializedParentAction#/anyOf/21` - [SerializedSetDeviceDefaultUserTimeoutAction](./serializedparentaction-definitions-serializedsetdevicedefaultusertimeoutaction.md) `https://timelimit.io/SerializedParentAction#/definitions/SerializedSetDeviceDefaultUserTimeoutAction`
- [SerializedSetDeviceUserAction](./serializedparentaction-definitions-serializedsetdeviceuseraction.md) `https://timelimit.io/SerializedParentAction#/definitions/SerializedSetDeviceUserAction` - [SerializedSetDeviceUserAction](./serializedparentaction-definitions-serializedsetdeviceuseraction.md) `https://timelimit.io/SerializedParentAction#/definitions/SerializedSetDeviceUserAction`
- [SerializedSetDeviceUserAction](./serializedparentaction-anyof-serializedsetdeviceuseraction.md) `https://timelimit.io/SerializedParentAction#/anyOf/21`
- [SerializedSetKeepSignedInAction](./serializedparentaction-definitions-serializedsetkeepsignedinaction.md) `https://timelimit.io/SerializedParentAction#/definitions/SerializedSetKeepSignedInAction` - [SerializedSetKeepSignedInAction](./serializedparentaction-definitions-serializedsetkeepsignedinaction.md) `https://timelimit.io/SerializedParentAction#/definitions/SerializedSetKeepSignedInAction`
- [SerializedSetKeepSignedInAction](./serializedparentaction-anyof-serializedsetkeepsignedinaction.md) `https://timelimit.io/SerializedParentAction#/anyOf/22` - [SerializedSetKeepSignedInAction](./serializedparentaction-anyof-serializedsetkeepsignedinaction.md) `https://timelimit.io/SerializedParentAction#/anyOf/22`
- [SerializedSetParentCategoryAction](./serializedparentaction-anyof-serializedsetparentcategoryaction.md) `https://timelimit.io/SerializedParentAction#/anyOf/23` - [SerializedSetParentCategoryAction](./serializedparentaction-anyof-serializedsetparentcategoryaction.md) `https://timelimit.io/SerializedParentAction#/anyOf/23`
- [SerializedSetParentCategoryAction](./serializedparentaction-definitions-serializedsetparentcategoryaction.md) `https://timelimit.io/SerializedParentAction#/definitions/SerializedSetParentCategoryAction` - [SerializedSetParentCategoryAction](./serializedparentaction-definitions-serializedsetparentcategoryaction.md) `https://timelimit.io/SerializedParentAction#/definitions/SerializedSetParentCategoryAction`
- [SerializedSetRelaxPrimaryDeviceAction](./serializedparentaction-definitions-serializedsetrelaxprimarydeviceaction.md) `https://timelimit.io/SerializedParentAction#/definitions/SerializedSetRelaxPrimaryDeviceAction`
- [SerializedSetRelaxPrimaryDeviceAction](./serializedparentaction-anyof-serializedsetrelaxprimarydeviceaction.md) `https://timelimit.io/SerializedParentAction#/anyOf/24` - [SerializedSetRelaxPrimaryDeviceAction](./serializedparentaction-anyof-serializedsetrelaxprimarydeviceaction.md) `https://timelimit.io/SerializedParentAction#/anyOf/24`
- [SerializedSetRelaxPrimaryDeviceAction](./serializedparentaction-definitions-serializedsetrelaxprimarydeviceaction.md) `https://timelimit.io/SerializedParentAction#/definitions/SerializedSetRelaxPrimaryDeviceAction`
- [SerializedSetSendDeviceConnected](./serializedparentaction-definitions-serializedsetsenddeviceconnected.md) `https://timelimit.io/SerializedParentAction#/definitions/SerializedSetSendDeviceConnected` - [SerializedSetSendDeviceConnected](./serializedparentaction-definitions-serializedsetsenddeviceconnected.md) `https://timelimit.io/SerializedParentAction#/definitions/SerializedSetSendDeviceConnected`
- [SerializedSetSendDeviceConnected](./serializedparentaction-anyof-serializedsetsenddeviceconnected.md) `https://timelimit.io/SerializedParentAction#/anyOf/25` - [SerializedSetSendDeviceConnected](./serializedparentaction-anyof-serializedsetsenddeviceconnected.md) `https://timelimit.io/SerializedParentAction#/anyOf/25`
- [SerializedSetUserDisableLimitsUntilAction](./serializedparentaction-definitions-serializedsetuserdisablelimitsuntilaction.md) `https://timelimit.io/SerializedParentAction#/definitions/SerializedSetUserDisableLimitsUntilAction` - [SerializedSetUserDisableLimitsUntilAction](./serializedparentaction-definitions-serializedsetuserdisablelimitsuntilaction.md) `https://timelimit.io/SerializedParentAction#/definitions/SerializedSetUserDisableLimitsUntilAction`
@@ -139,13 +141,13 @@
- [SerializedSetUserTimezoneAction](./serializedparentaction-anyof-serializedsetusertimezoneaction.md) `https://timelimit.io/SerializedParentAction#/anyOf/27` - [SerializedSetUserTimezoneAction](./serializedparentaction-anyof-serializedsetusertimezoneaction.md) `https://timelimit.io/SerializedParentAction#/anyOf/27`
- [SerializedSignOutAtDeviceAction](./serializedapplogicaction-definitions-serializedsignoutatdeviceaction.md) `https://timelimit.io/SerializedAppLogicAction#/definitions/SerializedSignOutAtDeviceAction` - [SerializedSignOutAtDeviceAction](./serializedapplogicaction-definitions-serializedsignoutatdeviceaction.md) `https://timelimit.io/SerializedAppLogicAction#/definitions/SerializedSignOutAtDeviceAction`
- [SerializedSignOutAtDeviceAction](./serializedapplogicaction-anyof-serializedsignoutatdeviceaction.md) `https://timelimit.io/SerializedAppLogicAction#/anyOf/5` - [SerializedSignOutAtDeviceAction](./serializedapplogicaction-anyof-serializedsignoutatdeviceaction.md) `https://timelimit.io/SerializedAppLogicAction#/anyOf/5`
- [SerializedTimeLimitRule](./serializedparentaction-definitions-serializedcreatetimelimtruleaction-properties-serializedtimelimitrule.md) `https://timelimit.io/SerializedParentAction#/definitions/SerializedCreateTimelimtRuleAction/properties/rule`
- [SerializedTimeLimitRule](./serializedparentaction-definitions-serializedtimelimitrule.md) `https://timelimit.io/SerializedParentAction#/definitions/SerializedTimeLimitRule` - [SerializedTimeLimitRule](./serializedparentaction-definitions-serializedtimelimitrule.md) `https://timelimit.io/SerializedParentAction#/definitions/SerializedTimeLimitRule`
- [SerializedTimeLimitRule](./serializedparentaction-definitions-serializedcreatetimelimtruleaction-properties-serializedtimelimitrule.md) `https://timelimit.io/SerializedParentAction#/definitions/SerializedCreateTimelimtRuleAction/properties/rule` - [SerializedTimeLimitRule](./serializedparentaction-definitions-serializedcreatetimelimtruleaction-properties-serializedtimelimitrule.md) `https://timelimit.io/SerializedParentAction#/definitions/SerializedCreateTimelimtRuleAction/properties/rule`
- [SerializedTimeLimitRule](./serializedparentaction-definitions-serializedcreatetimelimtruleaction-properties-serializedtimelimitrule.md) `https://timelimit.io/SerializedParentAction#/definitions/SerializedCreateTimelimtRuleAction/properties/rule`
- [SerializedUpdateAppActivitiesAction](./serializedapplogicaction-anyof-serializedupdateappactivitiesaction.md) `https://timelimit.io/SerializedAppLogicAction#/anyOf/7` - [SerializedUpdateAppActivitiesAction](./serializedapplogicaction-anyof-serializedupdateappactivitiesaction.md) `https://timelimit.io/SerializedAppLogicAction#/anyOf/7`
- [SerializedUpdateAppActivitiesAction](./serializedapplogicaction-definitions-serializedupdateappactivitiesaction.md) `https://timelimit.io/SerializedAppLogicAction#/definitions/SerializedUpdateAppActivitiesAction` - [SerializedUpdateAppActivitiesAction](./serializedapplogicaction-definitions-serializedupdateappactivitiesaction.md) `https://timelimit.io/SerializedAppLogicAction#/definitions/SerializedUpdateAppActivitiesAction`
- [SerializedUpdateCategoryBatteryLimitAction](./serializedparentaction-definitions-serializedupdatecategorybatterylimitaction.md) `https://timelimit.io/SerializedParentAction#/definitions/SerializedUpdateCategoryBatteryLimitAction`
- [SerializedUpdateCategoryBatteryLimitAction](./serializedparentaction-anyof-serializedupdatecategorybatterylimitaction.md) `https://timelimit.io/SerializedParentAction#/anyOf/28` - [SerializedUpdateCategoryBatteryLimitAction](./serializedparentaction-anyof-serializedupdatecategorybatterylimitaction.md) `https://timelimit.io/SerializedParentAction#/anyOf/28`
- [SerializedUpdateCategoryBatteryLimitAction](./serializedparentaction-definitions-serializedupdatecategorybatterylimitaction.md) `https://timelimit.io/SerializedParentAction#/definitions/SerializedUpdateCategoryBatteryLimitAction`
- [SerializedUpdateCategoryBlockAllNotificationsAction](./serializedparentaction-definitions-serializedupdatecategoryblockallnotificationsaction.md) `https://timelimit.io/SerializedParentAction#/definitions/SerializedUpdateCategoryBlockAllNotificationsAction` - [SerializedUpdateCategoryBlockAllNotificationsAction](./serializedparentaction-definitions-serializedupdatecategoryblockallnotificationsaction.md) `https://timelimit.io/SerializedParentAction#/definitions/SerializedUpdateCategoryBlockAllNotificationsAction`
- [SerializedUpdateCategoryBlockAllNotificationsAction](./serializedparentaction-anyof-serializedupdatecategoryblockallnotificationsaction.md) `https://timelimit.io/SerializedParentAction#/anyOf/29` - [SerializedUpdateCategoryBlockAllNotificationsAction](./serializedparentaction-anyof-serializedupdatecategoryblockallnotificationsaction.md) `https://timelimit.io/SerializedParentAction#/anyOf/29`
- [SerializedUpdateCategoryBlockedTimesAction](./serializedparentaction-definitions-serializedupdatecategoryblockedtimesaction.md) `https://timelimit.io/SerializedParentAction#/definitions/SerializedUpdateCategoryBlockedTimesAction` - [SerializedUpdateCategoryBlockedTimesAction](./serializedparentaction-definitions-serializedupdatecategoryblockedtimesaction.md) `https://timelimit.io/SerializedParentAction#/definitions/SerializedUpdateCategoryBlockedTimesAction`
@@ -154,63 +156,62 @@
- [SerializedUpdateCategorySortingAction](./serializedparentaction-anyof-serializedupdatecategorysortingaction.md) `https://timelimit.io/SerializedParentAction#/anyOf/31` - [SerializedUpdateCategorySortingAction](./serializedparentaction-anyof-serializedupdatecategorysortingaction.md) `https://timelimit.io/SerializedParentAction#/anyOf/31`
- [SerializedUpdateCategoryTemporarilyBlockedAction](./serializedparentaction-definitions-serializedupdatecategorytemporarilyblockedaction.md) `https://timelimit.io/SerializedParentAction#/definitions/SerializedUpdateCategoryTemporarilyBlockedAction` - [SerializedUpdateCategoryTemporarilyBlockedAction](./serializedparentaction-definitions-serializedupdatecategorytemporarilyblockedaction.md) `https://timelimit.io/SerializedParentAction#/definitions/SerializedUpdateCategoryTemporarilyBlockedAction`
- [SerializedUpdateCategoryTemporarilyBlockedAction](./serializedparentaction-anyof-serializedupdatecategorytemporarilyblockedaction.md) `https://timelimit.io/SerializedParentAction#/anyOf/32` - [SerializedUpdateCategoryTemporarilyBlockedAction](./serializedparentaction-anyof-serializedupdatecategorytemporarilyblockedaction.md) `https://timelimit.io/SerializedParentAction#/anyOf/32`
- [SerializedUpdateCategoryTimeWarningsAction](./serializedparentaction-definitions-serializedupdatecategorytimewarningsaction.md) `https://timelimit.io/SerializedParentAction#/definitions/SerializedUpdateCategoryTimeWarningsAction`
- [SerializedUpdateCategoryTimeWarningsAction](./serializedparentaction-anyof-serializedupdatecategorytimewarningsaction.md) `https://timelimit.io/SerializedParentAction#/anyOf/33` - [SerializedUpdateCategoryTimeWarningsAction](./serializedparentaction-anyof-serializedupdatecategorytimewarningsaction.md) `https://timelimit.io/SerializedParentAction#/anyOf/33`
- [SerializedUpdateCategoryTimeWarningsAction](./serializedparentaction-definitions-serializedupdatecategorytimewarningsaction.md) `https://timelimit.io/SerializedParentAction#/definitions/SerializedUpdateCategoryTimeWarningsAction`
- [SerializedUpdateCategoryTitleAction](./serializedparentaction-anyof-serializedupdatecategorytitleaction.md) `https://timelimit.io/SerializedParentAction#/anyOf/34` - [SerializedUpdateCategoryTitleAction](./serializedparentaction-anyof-serializedupdatecategorytitleaction.md) `https://timelimit.io/SerializedParentAction#/anyOf/34`
- [SerializedUpdateCategoryTitleAction](./serializedparentaction-definitions-serializedupdatecategorytitleaction.md) `https://timelimit.io/SerializedParentAction#/definitions/SerializedUpdateCategoryTitleAction` - [SerializedUpdateCategoryTitleAction](./serializedparentaction-definitions-serializedupdatecategorytitleaction.md) `https://timelimit.io/SerializedParentAction#/definitions/SerializedUpdateCategoryTitleAction`
- [SerializedUpdateDeviceNameAction](./serializedparentaction-anyof-serializedupdatedevicenameaction.md) `https://timelimit.io/SerializedParentAction#/anyOf/35`
- [SerializedUpdateDeviceNameAction](./serializedparentaction-definitions-serializedupdatedevicenameaction.md) `https://timelimit.io/SerializedParentAction#/definitions/SerializedUpdateDeviceNameAction` - [SerializedUpdateDeviceNameAction](./serializedparentaction-definitions-serializedupdatedevicenameaction.md) `https://timelimit.io/SerializedParentAction#/definitions/SerializedUpdateDeviceNameAction`
- [SerializedUpdateDeviceNameAction](./serializedparentaction-anyof-serializedupdatedevicenameaction.md) `https://timelimit.io/SerializedParentAction#/anyOf/35`
- [SerializedUpdateDeviceStatusAction](./serializedapplogicaction-anyof-serializedupdatedevicestatusaction.md) `https://timelimit.io/SerializedAppLogicAction#/anyOf/8` - [SerializedUpdateDeviceStatusAction](./serializedapplogicaction-anyof-serializedupdatedevicestatusaction.md) `https://timelimit.io/SerializedAppLogicAction#/anyOf/8`
- [SerializedUpdateDeviceStatusAction](./serializedapplogicaction-definitions-serializedupdatedevicestatusaction.md) `https://timelimit.io/SerializedAppLogicAction#/definitions/SerializedUpdateDeviceStatusAction` - [SerializedUpdateDeviceStatusAction](./serializedapplogicaction-definitions-serializedupdatedevicestatusaction.md) `https://timelimit.io/SerializedAppLogicAction#/definitions/SerializedUpdateDeviceStatusAction`
- [SerializedUpdateEnableActivityLevelBlockingAction](./serializedparentaction-definitions-serializedupdateenableactivitylevelblockingaction.md) `https://timelimit.io/SerializedParentAction#/definitions/SerializedUpdateEnableActivityLevelBlockingAction` - [SerializedUpdateEnableActivityLevelBlockingAction](./serializedparentaction-definitions-serializedupdateenableactivitylevelblockingaction.md) `https://timelimit.io/SerializedParentAction#/definitions/SerializedUpdateEnableActivityLevelBlockingAction`
- [SerializedUpdateEnableActivityLevelBlockingAction](./serializedparentaction-anyof-serializedupdateenableactivitylevelblockingaction.md) `https://timelimit.io/SerializedParentAction#/anyOf/36` - [SerializedUpdateEnableActivityLevelBlockingAction](./serializedparentaction-anyof-serializedupdateenableactivitylevelblockingaction.md) `https://timelimit.io/SerializedParentAction#/anyOf/36`
- [SerializedUpdateParentBlockedTimesAction](./serializedparentaction-anyof-serializedupdateparentblockedtimesaction.md) `https://timelimit.io/SerializedParentAction#/anyOf/38`
- [SerializedUpdateParentBlockedTimesAction](./serializedparentaction-definitions-serializedupdateparentblockedtimesaction.md) `https://timelimit.io/SerializedParentAction#/definitions/SerializedUpdateParentBlockedTimesAction` - [SerializedUpdateParentBlockedTimesAction](./serializedparentaction-definitions-serializedupdateparentblockedtimesaction.md) `https://timelimit.io/SerializedParentAction#/definitions/SerializedUpdateParentBlockedTimesAction`
- [SerializedUpdateParentBlockedTimesAction](./serializedparentaction-anyof-serializedupdateparentblockedtimesaction.md) `https://timelimit.io/SerializedParentAction#/anyOf/38`
- [SerializedUpdateParentNotificationFlagsAction](./serializedparentaction-definitions-serializedupdateparentnotificationflagsaction.md) `https://timelimit.io/SerializedParentAction#/definitions/SerializedUpdateParentNotificationFlagsAction` - [SerializedUpdateParentNotificationFlagsAction](./serializedparentaction-definitions-serializedupdateparentnotificationflagsaction.md) `https://timelimit.io/SerializedParentAction#/definitions/SerializedUpdateParentNotificationFlagsAction`
- [SerializedUpdateParentNotificationFlagsAction](./serializedparentaction-anyof-serializedupdateparentnotificationflagsaction.md) `https://timelimit.io/SerializedParentAction#/anyOf/39` - [SerializedUpdateParentNotificationFlagsAction](./serializedparentaction-anyof-serializedupdateparentnotificationflagsaction.md) `https://timelimit.io/SerializedParentAction#/anyOf/39`
- [SerializedUpdateTimelimitRuleAction](./serializedparentaction-definitions-serializedupdatetimelimitruleaction.md) `https://timelimit.io/SerializedParentAction#/definitions/SerializedUpdateTimelimitRuleAction` - [SerializedUpdateTimelimitRuleAction](./serializedparentaction-definitions-serializedupdatetimelimitruleaction.md) `https://timelimit.io/SerializedParentAction#/definitions/SerializedUpdateTimelimitRuleAction`
- [SerializedUpdateTimelimitRuleAction](./serializedparentaction-anyof-serializedupdatetimelimitruleaction.md) `https://timelimit.io/SerializedParentAction#/anyOf/40` - [SerializedUpdateTimelimitRuleAction](./serializedparentaction-anyof-serializedupdatetimelimitruleaction.md) `https://timelimit.io/SerializedParentAction#/anyOf/40`
- [SerializedUpdateUserFlagsAction](./serializedparentaction-definitions-serializedupdateuserflagsaction.md) `https://timelimit.io/SerializedParentAction#/definitions/SerializedUpdateUserFlagsAction` - [SerializedUpdateUserFlagsAction](./serializedparentaction-definitions-serializedupdateuserflagsaction.md) `https://timelimit.io/SerializedParentAction#/definitions/SerializedUpdateUserFlagsAction`
- [SerializedUpdateUserFlagsAction](./serializedparentaction-anyof-serializedupdateuserflagsaction.md) `https://timelimit.io/SerializedParentAction#/anyOf/41` - [SerializedUpdateUserFlagsAction](./serializedparentaction-anyof-serializedupdateuserflagsaction.md) `https://timelimit.io/SerializedParentAction#/anyOf/41`
- [SerializedUpdateUserLimitLoginCategory](./serializedparentaction-anyof-serializedupdateuserlimitlogincategory.md) `https://timelimit.io/SerializedParentAction#/anyOf/42`
- [SerializedUpdateUserLimitLoginCategory](./serializedparentaction-definitions-serializedupdateuserlimitlogincategory.md) `https://timelimit.io/SerializedParentAction#/definitions/SerializedUpdateUserLimitLoginCategory` - [SerializedUpdateUserLimitLoginCategory](./serializedparentaction-definitions-serializedupdateuserlimitlogincategory.md) `https://timelimit.io/SerializedParentAction#/definitions/SerializedUpdateUserLimitLoginCategory`
- [SerializedUpdateUserLimitLoginCategory](./serializedparentaction-anyof-serializedupdateuserlimitlogincategory.md) `https://timelimit.io/SerializedParentAction#/anyOf/42`
- [ServerCategoryNetworkId](./serverdatastatus-definitions-servercategorynetworkid.md) `https://timelimit.io/ServerDataStatus#/definitions/ServerCategoryNetworkId` - [ServerCategoryNetworkId](./serverdatastatus-definitions-servercategorynetworkid.md) `https://timelimit.io/ServerDataStatus#/definitions/ServerCategoryNetworkId`
- [ServerCategoryNetworkId](./serverdatastatus-definitions-serverupdatedcategorybasedata-properties-networks-servercategorynetworkid.md) `https://timelimit.io/ServerDataStatus#/definitions/ServerUpdatedCategoryBaseData/properties/networks/items` - [ServerCategoryNetworkId](./serverdatastatus-definitions-serverupdatedcategorybasedata-properties-networks-servercategorynetworkid.md) `https://timelimit.io/ServerDataStatus#/definitions/ServerUpdatedCategoryBaseData/properties/networks/items`
- [ServerCategoryNetworkId](./serverdatastatus-definitions-serverupdatedcategorybasedata-properties-networks-servercategorynetworkid.md) `https://timelimit.io/ServerDataStatus#/definitions/ServerUpdatedCategoryBaseData/properties/networks/items` - [ServerCategoryNetworkId](./serverdatastatus-definitions-serverupdatedcategorybasedata-properties-networks-servercategorynetworkid.md) `https://timelimit.io/ServerDataStatus#/definitions/ServerUpdatedCategoryBaseData/properties/networks/items`
- [ServerDeviceData](./serverdatastatus-definitions-serverdevicelist-properties-data-serverdevicedata.md) `https://timelimit.io/ServerDataStatus#/definitions/ServerDeviceList/properties/data/items` - [ServerDeviceData](./serverdatastatus-definitions-serverdevicelist-properties-data-serverdevicedata.md) `https://timelimit.io/ServerDataStatus#/definitions/ServerDeviceList/properties/data/items`
- [ServerDeviceData](./serverdatastatus-definitions-serverdevicedata.md) `https://timelimit.io/ServerDataStatus#/definitions/ServerDeviceData`
- [ServerDeviceData](./serverdatastatus-definitions-serverdevicelist-properties-data-serverdevicedata.md) `https://timelimit.io/ServerDataStatus#/definitions/ServerDeviceList/properties/data/items` - [ServerDeviceData](./serverdatastatus-definitions-serverdevicelist-properties-data-serverdevicedata.md) `https://timelimit.io/ServerDataStatus#/definitions/ServerDeviceList/properties/data/items`
- [ServerDeviceList](./serverdatastatus-properties-serverdevicelist.md) `https://timelimit.io/ServerDataStatus#/properties/devices` - [ServerDeviceData](./serverdatastatus-definitions-serverdevicedata.md) `https://timelimit.io/ServerDataStatus#/definitions/ServerDeviceData`
- [ServerDeviceList](./serverdatastatus-definitions-serverdevicelist.md) `https://timelimit.io/ServerDataStatus#/definitions/ServerDeviceList` - [ServerDeviceList](./serverdatastatus-definitions-serverdevicelist.md) `https://timelimit.io/ServerDataStatus#/definitions/ServerDeviceList`
- [ServerInstalledAppsData](./serverdatastatus-properties-apps-serverinstalledappsdata.md) `https://timelimit.io/ServerDataStatus#/properties/apps/items` - [ServerDeviceList](./serverdatastatus-properties-serverdevicelist.md) `https://timelimit.io/ServerDataStatus#/properties/devices`
- [ServerInstalledAppsData](./serverdatastatus-definitions-serverinstalledappsdata.md) `https://timelimit.io/ServerDataStatus#/definitions/ServerInstalledAppsData` - [ServerInstalledAppsData](./serverdatastatus-definitions-serverinstalledappsdata.md) `https://timelimit.io/ServerDataStatus#/definitions/ServerInstalledAppsData`
- [ServerInstalledAppsData](./serverdatastatus-properties-apps-serverinstalledappsdata.md) `https://timelimit.io/ServerDataStatus#/properties/apps/items`
- [ServerSessionDurationItem](./serverdatastatus-definitions-serverupdatedcategoryusedtimes-properties-sessiondurations-serversessiondurationitem.md) `https://timelimit.io/ServerDataStatus#/definitions/ServerUpdatedCategoryUsedTimes/properties/sessionDurations/items`
- [ServerSessionDurationItem](./serverdatastatus-definitions-serverupdatedcategoryusedtimes-properties-sessiondurations-serversessiondurationitem.md) `https://timelimit.io/ServerDataStatus#/definitions/ServerUpdatedCategoryUsedTimes/properties/sessionDurations/items` - [ServerSessionDurationItem](./serverdatastatus-definitions-serverupdatedcategoryusedtimes-properties-sessiondurations-serversessiondurationitem.md) `https://timelimit.io/ServerDataStatus#/definitions/ServerUpdatedCategoryUsedTimes/properties/sessionDurations/items`
- [ServerSessionDurationItem](./serverdatastatus-definitions-serversessiondurationitem.md) `https://timelimit.io/ServerDataStatus#/definitions/ServerSessionDurationItem` - [ServerSessionDurationItem](./serverdatastatus-definitions-serversessiondurationitem.md) `https://timelimit.io/ServerDataStatus#/definitions/ServerSessionDurationItem`
- [ServerSessionDurationItem](./serverdatastatus-definitions-serverupdatedcategoryusedtimes-properties-sessiondurations-serversessiondurationitem.md) `https://timelimit.io/ServerDataStatus#/definitions/ServerUpdatedCategoryUsedTimes/properties/sessionDurations/items` - [ServerTimeLimitRule](./serverdatastatus-definitions-serverupdatedtimelimitrules-properties-rules-servertimelimitrule.md) `https://timelimit.io/ServerDataStatus#/definitions/ServerUpdatedTimeLimitRules/properties/rules/items`
- [ServerTimeLimitRule](./serverdatastatus-definitions-servertimelimitrule.md) `https://timelimit.io/ServerDataStatus#/definitions/ServerTimeLimitRule` - [ServerTimeLimitRule](./serverdatastatus-definitions-servertimelimitrule.md) `https://timelimit.io/ServerDataStatus#/definitions/ServerTimeLimitRule`
- [ServerTimeLimitRule](./serverdatastatus-definitions-serverupdatedtimelimitrules-properties-rules-servertimelimitrule.md) `https://timelimit.io/ServerDataStatus#/definitions/ServerUpdatedTimeLimitRules/properties/rules/items` - [ServerTimeLimitRule](./serverdatastatus-definitions-serverupdatedtimelimitrules-properties-rules-servertimelimitrule.md) `https://timelimit.io/ServerDataStatus#/definitions/ServerUpdatedTimeLimitRules/properties/rules/items`
- [ServerTimeLimitRule](./serverdatastatus-definitions-serverupdatedtimelimitrules-properties-rules-servertimelimitrule.md) `https://timelimit.io/ServerDataStatus#/definitions/ServerUpdatedTimeLimitRules/properties/rules/items`
- [ServerUpdatedCategoryAssignedApps](./serverdatastatus-definitions-serverupdatedcategoryassignedapps.md) `https://timelimit.io/ServerDataStatus#/definitions/ServerUpdatedCategoryAssignedApps`
- [ServerUpdatedCategoryAssignedApps](./serverdatastatus-properties-categoryapp-serverupdatedcategoryassignedapps.md) `https://timelimit.io/ServerDataStatus#/properties/categoryApp/items` - [ServerUpdatedCategoryAssignedApps](./serverdatastatus-properties-categoryapp-serverupdatedcategoryassignedapps.md) `https://timelimit.io/ServerDataStatus#/properties/categoryApp/items`
- [ServerUpdatedCategoryAssignedApps](./serverdatastatus-definitions-serverupdatedcategoryassignedapps.md) `https://timelimit.io/ServerDataStatus#/definitions/ServerUpdatedCategoryAssignedApps`
- [ServerUpdatedCategoryBaseData](./serverdatastatus-properties-categorybase-serverupdatedcategorybasedata.md) `https://timelimit.io/ServerDataStatus#/properties/categoryBase/items` - [ServerUpdatedCategoryBaseData](./serverdatastatus-properties-categorybase-serverupdatedcategorybasedata.md) `https://timelimit.io/ServerDataStatus#/properties/categoryBase/items`
- [ServerUpdatedCategoryBaseData](./serverdatastatus-definitions-serverupdatedcategorybasedata.md) `https://timelimit.io/ServerDataStatus#/definitions/ServerUpdatedCategoryBaseData` - [ServerUpdatedCategoryBaseData](./serverdatastatus-definitions-serverupdatedcategorybasedata.md) `https://timelimit.io/ServerDataStatus#/definitions/ServerUpdatedCategoryBaseData`
- [ServerUpdatedCategoryUsedTimes](./serverdatastatus-definitions-serverupdatedcategoryusedtimes.md) `https://timelimit.io/ServerDataStatus#/definitions/ServerUpdatedCategoryUsedTimes` - [ServerUpdatedCategoryUsedTimes](./serverdatastatus-definitions-serverupdatedcategoryusedtimes.md) `https://timelimit.io/ServerDataStatus#/definitions/ServerUpdatedCategoryUsedTimes`
- [ServerUpdatedCategoryUsedTimes](./serverdatastatus-properties-usedtimes-serverupdatedcategoryusedtimes.md) `https://timelimit.io/ServerDataStatus#/properties/usedTimes/items` - [ServerUpdatedCategoryUsedTimes](./serverdatastatus-properties-usedtimes-serverupdatedcategoryusedtimes.md) `https://timelimit.io/ServerDataStatus#/properties/usedTimes/items`
- [ServerUpdatedTimeLimitRules](./serverdatastatus-definitions-serverupdatedtimelimitrules.md) `https://timelimit.io/ServerDataStatus#/definitions/ServerUpdatedTimeLimitRules`
- [ServerUpdatedTimeLimitRules](./serverdatastatus-properties-rules-serverupdatedtimelimitrules.md) `https://timelimit.io/ServerDataStatus#/properties/rules/items` - [ServerUpdatedTimeLimitRules](./serverdatastatus-properties-rules-serverupdatedtimelimitrules.md) `https://timelimit.io/ServerDataStatus#/properties/rules/items`
- [ServerUpdatedTimeLimitRules](./serverdatastatus-definitions-serverupdatedtimelimitrules.md) `https://timelimit.io/ServerDataStatus#/definitions/ServerUpdatedTimeLimitRules`
- [ServerUsedTimeItem](./serverdatastatus-definitions-serverusedtimeitem.md) `https://timelimit.io/ServerDataStatus#/definitions/ServerUsedTimeItem` - [ServerUsedTimeItem](./serverdatastatus-definitions-serverusedtimeitem.md) `https://timelimit.io/ServerDataStatus#/definitions/ServerUsedTimeItem`
- [ServerUsedTimeItem](./serverdatastatus-definitions-serverupdatedcategoryusedtimes-properties-times-serverusedtimeitem.md) `https://timelimit.io/ServerDataStatus#/definitions/ServerUpdatedCategoryUsedTimes/properties/times/items` - [ServerUsedTimeItem](./serverdatastatus-definitions-serverupdatedcategoryusedtimes-properties-times-serverusedtimeitem.md) `https://timelimit.io/ServerDataStatus#/definitions/ServerUpdatedCategoryUsedTimes/properties/times/items`
- [ServerUsedTimeItem](./serverdatastatus-definitions-serverupdatedcategoryusedtimes-properties-times-serverusedtimeitem.md) `https://timelimit.io/ServerDataStatus#/definitions/ServerUpdatedCategoryUsedTimes/properties/times/items` - [ServerUsedTimeItem](./serverdatastatus-definitions-serverupdatedcategoryusedtimes-properties-times-serverusedtimeitem.md) `https://timelimit.io/ServerDataStatus#/definitions/ServerUpdatedCategoryUsedTimes/properties/times/items`
- [ServerUserEntry](./serverdatastatus-definitions-serveruserlist-properties-data-serveruserentry.md) `https://timelimit.io/ServerDataStatus#/definitions/ServerUserList/properties/data/items`
- [ServerUserEntry](./serverdatastatus-definitions-serveruserlist-properties-data-serveruserentry.md) `https://timelimit.io/ServerDataStatus#/definitions/ServerUserList/properties/data/items`
- [ServerUserEntry](./serverdatastatus-definitions-serveruserentry.md) `https://timelimit.io/ServerDataStatus#/definitions/ServerUserEntry` - [ServerUserEntry](./serverdatastatus-definitions-serveruserentry.md) `https://timelimit.io/ServerDataStatus#/definitions/ServerUserEntry`
- [ServerUserEntry](./serverdatastatus-definitions-serveruserlist-properties-data-serveruserentry.md) `https://timelimit.io/ServerDataStatus#/definitions/ServerUserList/properties/data/items`
- [ServerUserEntry](./serverdatastatus-definitions-serveruserlist-properties-data-serveruserentry.md) `https://timelimit.io/ServerDataStatus#/definitions/ServerUserList/properties/data/items`
- [ServerUserList](./serverdatastatus-properties-serveruserlist.md) `https://timelimit.io/ServerDataStatus#/properties/users` - [ServerUserList](./serverdatastatus-properties-serveruserlist.md) `https://timelimit.io/ServerDataStatus#/properties/users`
- [ServerUserList](./serverdatastatus-definitions-serveruserlist.md) `https://timelimit.io/ServerDataStatus#/definitions/ServerUserList` - [ServerUserList](./serverdatastatus-definitions-serveruserlist.md) `https://timelimit.io/ServerDataStatus#/definitions/ServerUserList`
- [Untitled object in ClientPullChangesRequest](./clientpullchangesrequest-definitions-clientdatastatus-properties-apps.md) `https://timelimit.io/ClientPullChangesRequest#/definitions/ClientDataStatus/properties/apps` - [Untitled object in ClientPullChangesRequest](./clientpullchangesrequest-definitions-clientdatastatus-properties-apps.md) `https://timelimit.io/ClientPullChangesRequest#/definitions/ClientDataStatus/properties/apps`
- [Untitled object in ClientPullChangesRequest](./clientpullchangesrequest-definitions-clientdatastatus-properties-categories.md) `https://timelimit.io/ClientPullChangesRequest#/definitions/ClientDataStatus/properties/categories`
- [Untitled object in ClientPullChangesRequest](./clientpullchangesrequest-definitions-clientdatastatus-properties-apps.md) `https://timelimit.io/ClientPullChangesRequest#/definitions/ClientDataStatus/properties/apps` - [Untitled object in ClientPullChangesRequest](./clientpullchangesrequest-definitions-clientdatastatus-properties-apps.md) `https://timelimit.io/ClientPullChangesRequest#/definitions/ClientDataStatus/properties/apps`
- [Untitled object in ClientPullChangesRequest](./clientpullchangesrequest-definitions-clientdatastatus-properties-categories.md) `https://timelimit.io/ClientPullChangesRequest#/definitions/ClientDataStatus/properties/categories` - [Untitled object in ClientPullChangesRequest](./clientpullchangesrequest-definitions-clientdatastatus-properties-categories.md) `https://timelimit.io/ClientPullChangesRequest#/definitions/ClientDataStatus/properties/categories`
- [Untitled object in ClientPushChangesRequest](./clientpushchangesrequest-properties-actions-items.md) `https://timelimit.io/ClientPushChangesRequest#/properties/actions/items` - [Untitled object in ClientPullChangesRequest](./clientpullchangesrequest-definitions-clientdatastatus-properties-categories.md) `https://timelimit.io/ClientPullChangesRequest#/definitions/ClientDataStatus/properties/categories`
- [Untitled object in SerializedAppLogicAction](./serializedapplogicaction-definitions-serializedaddusedtimeactionversion2-properties-i-items.md) `https://timelimit.io/SerializedAppLogicAction#/definitions/SerializedAddUsedTimeActionVersion2/properties/i/items` - [Untitled object in SerializedAppLogicAction](./serializedapplogicaction-definitions-serializedaddusedtimeactionversion2-properties-i-items.md) `https://timelimit.io/SerializedAppLogicAction#/definitions/SerializedAddUsedTimeActionVersion2/properties/i/items`
- [Untitled object in SerializedAppLogicAction](./serializedapplogicaction-definitions-serializedaddusedtimeactionversion2-properties-i-items.md) `https://timelimit.io/SerializedAppLogicAction#/definitions/SerializedAddUsedTimeActionVersion2/properties/i/items` - [Untitled object in SerializedAppLogicAction](./serializedapplogicaction-definitions-serializedaddusedtimeactionversion2-properties-i-items.md) `https://timelimit.io/SerializedAppLogicAction#/definitions/SerializedAddUsedTimeActionVersion2/properties/i/items`
@@ -0,0 +1,16 @@
# Untitled string in ClientPushChangesRequest Schema
```txt
https://timelimit.io/ClientPushChangesRequest#/definitions/ClientPushChangesRequestAction/properties/encodedAction
```
| Abstract | Extensible | Status | Identifiable | Custom Properties | Additional Properties | Access Restrictions | Defined In |
| :------------------ | ---------- | -------------- | ----------------------- | :---------------- | --------------------- | ------------------- | ----------------------------------------------------------------------------------------------------- |
| Can be instantiated | No | Unknown status | Unknown identifiability | Forbidden | Allowed | none | [ClientPushChangesRequest.schema.json\*](ClientPushChangesRequest.schema.json "open original schema") |
## encodedAction Type
`string`
@@ -0,0 +1,16 @@
# Untitled string in ClientPushChangesRequest Schema
```txt
https://timelimit.io/ClientPushChangesRequest#/definitions/ClientPushChangesRequestAction/properties/integrity
```
| Abstract | Extensible | Status | Identifiable | Custom Properties | Additional Properties | Access Restrictions | Defined In |
| :------------------ | ---------- | -------------- | ----------------------- | :---------------- | --------------------- | ------------------- | ----------------------------------------------------------------------------------------------------- |
| Can be instantiated | No | Unknown status | Unknown identifiability | Forbidden | Allowed | none | [ClientPushChangesRequest.schema.json\*](ClientPushChangesRequest.schema.json "open original schema") |
## integrity Type
`string`
@@ -0,0 +1,16 @@
# Untitled number in ClientPushChangesRequest Schema
```txt
https://timelimit.io/ClientPushChangesRequest#/definitions/ClientPushChangesRequestAction/properties/sequenceNumber
```
| Abstract | Extensible | Status | Identifiable | Custom Properties | Additional Properties | Access Restrictions | Defined In |
| :------------------ | ---------- | -------------- | ----------------------- | :---------------- | --------------------- | ------------------- | ----------------------------------------------------------------------------------------------------- |
| Can be instantiated | No | Unknown status | Unknown identifiability | Forbidden | Allowed | none | [ClientPushChangesRequest.schema.json\*](ClientPushChangesRequest.schema.json "open original schema") |
## sequenceNumber Type
`number`
@@ -0,0 +1,26 @@
# Untitled string in ClientPushChangesRequest Schema
```txt
https://timelimit.io/ClientPushChangesRequest#/definitions/ClientPushChangesRequestAction/properties/type
```
| Abstract | Extensible | Status | Identifiable | Custom Properties | Additional Properties | Access Restrictions | Defined In |
| :------------------ | ---------- | -------------- | ----------------------- | :---------------- | --------------------- | ------------------- | ----------------------------------------------------------------------------------------------------- |
| Can be instantiated | No | Unknown status | Unknown identifiability | Forbidden | Allowed | none | [ClientPushChangesRequest.schema.json\*](ClientPushChangesRequest.schema.json "open original schema") |
## type Type
`string`
## type Constraints
**enum**: the value of this property must be equal to one of the following values:
| Value | Explanation |
| :----------- | ----------- |
| `"appLogic"` | |
| `"child"` | |
| `"parent"` | |
@@ -0,0 +1,16 @@
# Untitled string in ClientPushChangesRequest Schema
```txt
https://timelimit.io/ClientPushChangesRequest#/definitions/ClientPushChangesRequestAction/properties/userId
```
| Abstract | Extensible | Status | Identifiable | Custom Properties | Additional Properties | Access Restrictions | Defined In |
| :------------------ | ---------- | -------------- | ----------------------- | :---------------- | --------------------- | ------------------- | ----------------------------------------------------------------------------------------------------- |
| Can be instantiated | No | Unknown status | Unknown identifiability | Forbidden | Allowed | none | [ClientPushChangesRequest.schema.json\*](ClientPushChangesRequest.schema.json "open original schema") |
## userId Type
`string`
@@ -0,0 +1,16 @@
# Untitled undefined type in ClientPushChangesRequest Schema
```txt
https://timelimit.io/ClientPushChangesRequest#/definitions/ClientPushChangesRequestAction/properties
```
| Abstract | Extensible | Status | Identifiable | Custom Properties | Additional Properties | Access Restrictions | Defined In |
| :------------------ | ---------- | -------------- | ----------------------- | :---------------- | --------------------- | ------------------- | ----------------------------------------------------------------------------------------------------- |
| Can be instantiated | No | Unknown status | Unknown identifiability | Forbidden | Allowed | none | [ClientPushChangesRequest.schema.json\*](ClientPushChangesRequest.schema.json "open original schema") |
## properties Type
unknown
@@ -0,0 +1,116 @@
# ClientPushChangesRequestAction Schema
```txt
https://timelimit.io/ClientPushChangesRequest#/definitions/ClientPushChangesRequestAction
```
| Abstract | Extensible | Status | Identifiable | Custom Properties | Additional Properties | Access Restrictions | Defined In |
| :------------------ | ---------- | -------------- | ------------ | :---------------- | --------------------- | ------------------- | ----------------------------------------------------------------------------------------------------- |
| Can be instantiated | No | Unknown status | No | Forbidden | Forbidden | none | [ClientPushChangesRequest.schema.json\*](ClientPushChangesRequest.schema.json "open original schema") |
## ClientPushChangesRequestAction Type
`object` ([ClientPushChangesRequestAction](clientpushchangesrequest-definitions-clientpushchangesrequestaction.md))
# ClientPushChangesRequestAction Properties
| Property | Type | Required | Nullable | Defined by |
| :-------------------------------- | -------- | -------- | -------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| [encodedAction](#encodedAction) | `string` | Required | cannot be null | [ClientPushChangesRequest](clientpushchangesrequest-definitions-clientpushchangesrequestaction-properties-encodedaction.md "https://timelimit.io/ClientPushChangesRequest#/definitions/ClientPushChangesRequestAction/properties/encodedAction") |
| [sequenceNumber](#sequenceNumber) | `number` | Required | cannot be null | [ClientPushChangesRequest](clientpushchangesrequest-definitions-clientpushchangesrequestaction-properties-sequencenumber.md "https://timelimit.io/ClientPushChangesRequest#/definitions/ClientPushChangesRequestAction/properties/sequenceNumber") |
| [integrity](#integrity) | `string` | Required | cannot be null | [ClientPushChangesRequest](clientpushchangesrequest-definitions-clientpushchangesrequestaction-properties-integrity.md "https://timelimit.io/ClientPushChangesRequest#/definitions/ClientPushChangesRequestAction/properties/integrity") |
| [type](#type) | `string` | Required | cannot be null | [ClientPushChangesRequest](clientpushchangesrequest-definitions-clientpushchangesrequestaction-properties-type.md "https://timelimit.io/ClientPushChangesRequest#/definitions/ClientPushChangesRequestAction/properties/type") |
| [userId](#userId) | `string` | Required | cannot be null | [ClientPushChangesRequest](clientpushchangesrequest-definitions-clientpushchangesrequestaction-properties-userid.md "https://timelimit.io/ClientPushChangesRequest#/definitions/ClientPushChangesRequestAction/properties/userId") |
## encodedAction
`encodedAction`
- is required
- Type: `string`
- cannot be null
- defined in: [ClientPushChangesRequest](clientpushchangesrequest-definitions-clientpushchangesrequestaction-properties-encodedaction.md "https://timelimit.io/ClientPushChangesRequest#/definitions/ClientPushChangesRequestAction/properties/encodedAction")
### encodedAction Type
`string`
## sequenceNumber
`sequenceNumber`
- is required
- Type: `number`
- cannot be null
- defined in: [ClientPushChangesRequest](clientpushchangesrequest-definitions-clientpushchangesrequestaction-properties-sequencenumber.md "https://timelimit.io/ClientPushChangesRequest#/definitions/ClientPushChangesRequestAction/properties/sequenceNumber")
### sequenceNumber Type
`number`
## integrity
`integrity`
- is required
- Type: `string`
- cannot be null
- defined in: [ClientPushChangesRequest](clientpushchangesrequest-definitions-clientpushchangesrequestaction-properties-integrity.md "https://timelimit.io/ClientPushChangesRequest#/definitions/ClientPushChangesRequestAction/properties/integrity")
### integrity Type
`string`
## type
`type`
- is required
- Type: `string`
- cannot be null
- defined in: [ClientPushChangesRequest](clientpushchangesrequest-definitions-clientpushchangesrequestaction-properties-type.md "https://timelimit.io/ClientPushChangesRequest#/definitions/ClientPushChangesRequestAction/properties/type")
### type Type
`string`
### type Constraints
**enum**: the value of this property must be equal to one of the following values:
| Value | Explanation |
| :----------- | ----------- |
| `"appLogic"` | |
| `"child"` | |
| `"parent"` | |
## userId
`userId`
- is required
- Type: `string`
- cannot be null
- defined in: [ClientPushChangesRequest](clientpushchangesrequest-definitions-clientpushchangesrequestaction-properties-userid.md "https://timelimit.io/ClientPushChangesRequest#/definitions/ClientPushChangesRequestAction/properties/userId")
### userId Type
`string`
@@ -0,0 +1,16 @@
# Untitled undefined type in ClientPushChangesRequest Schema
```txt
https://timelimit.io/ClientPushChangesRequest#/definitions
```
| Abstract | Extensible | Status | Identifiable | Custom Properties | Additional Properties | Access Restrictions | Defined In |
| :------------------ | ---------- | -------------- | ----------------------- | :---------------- | --------------------- | ------------------- | ----------------------------------------------------------------------------------------------------- |
| Can be instantiated | No | Unknown status | Unknown identifiability | Forbidden | Allowed | none | [ClientPushChangesRequest.schema.json\*](ClientPushChangesRequest.schema.json "open original schema") |
## definitions Type
unknown
@@ -13,4 +13,4 @@ https://timelimit.io/ClientPushChangesRequest#/properties/actions
## actions Type ## actions Type
`object[]` ([Details](clientpushchangesrequest-properties-actions-items.md)) `object[]` ([ClientPushChangesRequestAction](clientpushchangesrequest-definitions-clientpushchangesrequestaction.md))
+111 -3
View File
@@ -9,12 +9,120 @@ https://timelimit.io/ClientPushChangesRequest
| Abstract | Extensible | Status | Identifiable | Custom Properties | Additional Properties | Access Restrictions | Defined In | | Abstract | Extensible | Status | Identifiable | Custom Properties | Additional Properties | Access Restrictions | Defined In |
| :------------------ | ---------- | -------------- | ------------ | :---------------- | --------------------- | ------------------- | --------------------------------------------------------------------------------------------------- | | :------------------ | ---------- | -------------- | ------------ | :---------------- | --------------------- | ------------------- | --------------------------------------------------------------------------------------------------- |
| Can be instantiated | No | Unknown status | No | Forbidden | Forbidden | none | [ClientPushChangesRequest.schema.json](ClientPushChangesRequest.schema.json "open original schema") | | Can be instantiated | Yes | Unknown status | No | Forbidden | Forbidden | none | [ClientPushChangesRequest.schema.json](ClientPushChangesRequest.schema.json "open original schema") |
## ClientPushChangesRequest Type ## ClientPushChangesRequest Type
`object` ([ClientPushChangesRequest](clientpushchangesrequest.md)) `object` ([ClientPushChangesRequest](clientpushchangesrequest.md))
# ClientPushChangesRequest Definitions
## Definitions group ClientPushChangesRequestAction
Reference this group by using
```json
{"$ref":"https://timelimit.io/ClientPushChangesRequest#/definitions/ClientPushChangesRequestAction"}
```
| Property | Type | Required | Nullable | Defined by |
| :-------------------------------- | -------- | -------- | -------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| [encodedAction](#encodedAction) | `string` | Required | cannot be null | [ClientPushChangesRequest](clientpushchangesrequest-definitions-clientpushchangesrequestaction-properties-encodedaction.md "https://timelimit.io/ClientPushChangesRequest#/definitions/ClientPushChangesRequestAction/properties/encodedAction") |
| [sequenceNumber](#sequenceNumber) | `number` | Required | cannot be null | [ClientPushChangesRequest](clientpushchangesrequest-definitions-clientpushchangesrequestaction-properties-sequencenumber.md "https://timelimit.io/ClientPushChangesRequest#/definitions/ClientPushChangesRequestAction/properties/sequenceNumber") |
| [integrity](#integrity) | `string` | Required | cannot be null | [ClientPushChangesRequest](clientpushchangesrequest-definitions-clientpushchangesrequestaction-properties-integrity.md "https://timelimit.io/ClientPushChangesRequest#/definitions/ClientPushChangesRequestAction/properties/integrity") |
| [type](#type) | `string` | Required | cannot be null | [ClientPushChangesRequest](clientpushchangesrequest-definitions-clientpushchangesrequestaction-properties-type.md "https://timelimit.io/ClientPushChangesRequest#/definitions/ClientPushChangesRequestAction/properties/type") |
| [userId](#userId) | `string` | Required | cannot be null | [ClientPushChangesRequest](clientpushchangesrequest-definitions-clientpushchangesrequestaction-properties-userid.md "https://timelimit.io/ClientPushChangesRequest#/definitions/ClientPushChangesRequestAction/properties/userId") |
### encodedAction
`encodedAction`
- is required
- Type: `string`
- cannot be null
- defined in: [ClientPushChangesRequest](clientpushchangesrequest-definitions-clientpushchangesrequestaction-properties-encodedaction.md "https://timelimit.io/ClientPushChangesRequest#/definitions/ClientPushChangesRequestAction/properties/encodedAction")
#### encodedAction Type
`string`
### sequenceNumber
`sequenceNumber`
- is required
- Type: `number`
- cannot be null
- defined in: [ClientPushChangesRequest](clientpushchangesrequest-definitions-clientpushchangesrequestaction-properties-sequencenumber.md "https://timelimit.io/ClientPushChangesRequest#/definitions/ClientPushChangesRequestAction/properties/sequenceNumber")
#### sequenceNumber Type
`number`
### integrity
`integrity`
- is required
- Type: `string`
- cannot be null
- defined in: [ClientPushChangesRequest](clientpushchangesrequest-definitions-clientpushchangesrequestaction-properties-integrity.md "https://timelimit.io/ClientPushChangesRequest#/definitions/ClientPushChangesRequestAction/properties/integrity")
#### integrity Type
`string`
### type
`type`
- is required
- Type: `string`
- cannot be null
- defined in: [ClientPushChangesRequest](clientpushchangesrequest-definitions-clientpushchangesrequestaction-properties-type.md "https://timelimit.io/ClientPushChangesRequest#/definitions/ClientPushChangesRequestAction/properties/type")
#### type Type
`string`
#### type Constraints
**enum**: the value of this property must be equal to one of the following values:
| Value | Explanation |
| :----------- | ----------- |
| `"appLogic"` | |
| `"child"` | |
| `"parent"` | |
### userId
`userId`
- is required
- Type: `string`
- cannot be null
- defined in: [ClientPushChangesRequest](clientpushchangesrequest-definitions-clientpushchangesrequestaction-properties-userid.md "https://timelimit.io/ClientPushChangesRequest#/definitions/ClientPushChangesRequestAction/properties/userId")
#### userId Type
`string`
# ClientPushChangesRequest Properties # ClientPushChangesRequest Properties
| Property | Type | Required | Nullable | Defined by | | Property | Type | Required | Nullable | Defined by |
@@ -46,10 +154,10 @@ https://timelimit.io/ClientPushChangesRequest
`actions` `actions`
- is required - is required
- Type: `object[]` ([Details](clientpushchangesrequest-properties-actions-items.md)) - Type: `object[]` ([ClientPushChangesRequestAction](clientpushchangesrequest-definitions-clientpushchangesrequestaction.md))
- cannot be null - cannot be null
- defined in: [ClientPushChangesRequest](clientpushchangesrequest-properties-actions.md "https://timelimit.io/ClientPushChangesRequest#/properties/actions") - defined in: [ClientPushChangesRequest](clientpushchangesrequest-properties-actions.md "https://timelimit.io/ClientPushChangesRequest#/properties/actions")
### actions Type ### actions Type
`object[]` ([Details](clientpushchangesrequest-properties-actions-items.md)) `object[]` ([ClientPushChangesRequestAction](clientpushchangesrequest-definitions-clientpushchangesrequestaction.md))
+17 -23
View File
@@ -1851,7 +1851,6 @@
"version": "7.1.6", "version": "7.1.6",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz",
"integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==",
"dev": true,
"requires": { "requires": {
"fs.realpath": "1.0.0", "fs.realpath": "1.0.0",
"inflight": "1.0.6", "inflight": "1.0.6",
@@ -2818,9 +2817,20 @@
"npm-packlist": "1.4.1", "npm-packlist": "1.4.1",
"npmlog": "4.1.2", "npmlog": "4.1.2",
"rc": "1.2.8", "rc": "1.2.8",
"rimraf": "2.6.3", "rimraf": "2.7.1",
"semver": "5.5.0", "semver": "5.5.0",
"tar": "4.4.8" "tar": "4.4.8"
},
"dependencies": {
"rimraf": {
"version": "2.7.1",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz",
"integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==",
"optional": true,
"requires": {
"glob": "7.1.6"
}
}
} }
}, },
"nodemailer": { "nodemailer": {
@@ -3559,28 +3569,12 @@
} }
}, },
"rimraf": { "rimraf": {
"version": "2.6.3", "version": "3.0.2",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
"integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
"optional": true, "dev": true,
"requires": { "requires": {
"glob": "7.1.3" "glob": "7.1.6"
},
"dependencies": {
"glob": {
"version": "7.1.3",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz",
"integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==",
"optional": true,
"requires": {
"fs.realpath": "1.0.0",
"inflight": "1.0.6",
"inherits": "2.0.3",
"minimatch": "3.0.4",
"once": "1.4.0",
"path-is-absolute": "1.0.1"
}
}
} }
}, },
"safe-buffer": { "safe-buffer": {
+3 -1
View File
@@ -8,7 +8,8 @@
"test": "echo \"Error: no test specified\" && exit 1", "test": "echo \"Error: no test specified\" && exit 1",
"lint": "tslint --project .", "lint": "tslint --project .",
"lint:fix": "tslint --project . --fix", "lint:fix": "tslint --project . --fix",
"build": "npm run build:json && npm run build:ts && npm run lint && npm run build:doc", "build": "npm run build:clean && npm run build:json && npm run build:ts && npm run lint && npm run build:doc",
"build:clean": "rimraf build",
"build:json": "node ./scripts/build-schemas.js", "build:json": "node ./scripts/build-schemas.js",
"build:ts": "tsc", "build:ts": "tsc",
"build:doc": "npm run build:doc:json", "build:doc": "npm run build:doc:json",
@@ -39,6 +40,7 @@
"@types/socket.io": "^2.1.4", "@types/socket.io": "^2.1.4",
"@types/tokgen": "^1.0.0", "@types/tokgen": "^1.0.0",
"@types/umzug": "^2.2.3", "@types/umzug": "^2.2.3",
"rimraf": "^3.0.2",
"tslint": "^6.1.3", "tslint": "^6.1.3",
"tslint-config-standard": "^9.0.0", "tslint-config-standard": "^9.0.0",
"typescript": "^3.8.3", "typescript": "^3.8.3",
+5 -4
View File
@@ -15,9 +15,10 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { assertNonEmptyListWithoutDuplicates } from '../util/list'
import { assertIdWithinFamily } from '../util/token'
import { ParentAction } from './basetypes' import { ParentAction } from './basetypes'
import { assertIdWithinFamily, assertNonEmptyListWithoutDuplicates } from './meta/util'
const actionType = 'AddCategoryApps'
export class AddCategoryAppsAction extends ParentAction { export class AddCategoryAppsAction extends ParentAction {
readonly categoryId: string readonly categoryId: string
@@ -26,8 +27,8 @@ export class AddCategoryAppsAction extends ParentAction {
constructor ({ categoryId, packageNames }: {categoryId: string, packageNames: Array<string>}) { constructor ({ categoryId, packageNames }: {categoryId: string, packageNames: Array<string>}) {
super() super()
assertIdWithinFamily(categoryId) assertIdWithinFamily({ actionType, field: 'categoryId', value: categoryId })
assertNonEmptyListWithoutDuplicates(packageNames) assertNonEmptyListWithoutDuplicates({ actionType, field: 'packageNames', list: packageNames })
this.categoryId = categoryId this.categoryId = categoryId
this.packageNames = packageNames this.packageNames = packageNames
+14 -6
View File
@@ -16,9 +16,11 @@
*/ */
import { anonymizedNetworkIdLength } from '../database/categorynetworkid' import { anonymizedNetworkIdLength } from '../database/categorynetworkid'
import { assertIsHexString } from '../util/hexstring'
import { assertIdWithinFamily } from '../util/token'
import { ParentAction } from './basetypes' import { ParentAction } from './basetypes'
import { InvalidActionParameterException } from './meta/exception'
import { assertHexString, assertIdWithinFamily } from './meta/util'
const actionType = 'AddCategoryNetworkIdAction'
export class AddCategoryNetworkIdAction extends ParentAction { export class AddCategoryNetworkIdAction extends ParentAction {
readonly categoryId: string readonly categoryId: string
@@ -32,10 +34,16 @@ export class AddCategoryNetworkIdAction extends ParentAction {
}) { }) {
super() super()
assertIdWithinFamily(categoryId) assertIdWithinFamily({ actionType, field: 'categoryId', value: categoryId })
assertIdWithinFamily(itemId) assertIdWithinFamily({ actionType, field: 'itemId', value: itemId })
assertIsHexString(hashedNetworkId) assertHexString({ actionType, field: 'hashedNetworkId', value: hashedNetworkId })
if (hashedNetworkId.length !== anonymizedNetworkIdLength) throw new Error('wrong network id length')
if (hashedNetworkId.length !== anonymizedNetworkIdLength) {
throw new InvalidActionParameterException({
actionType,
staticMessage: 'wrong network id length'
})
}
this.categoryId = categoryId this.categoryId = categoryId
this.itemId = itemId this.itemId = itemId
+4 -2
View File
@@ -16,8 +16,10 @@
*/ */
import { InstalledApp, SerializedInstalledApp } from '../model/installedapp' import { InstalledApp, SerializedInstalledApp } from '../model/installedapp'
import { assertNonEmptyListWithoutDuplicates } from '../util/list'
import { AppLogicAction } from './basetypes' import { AppLogicAction } from './basetypes'
import { assertNonEmptyListWithoutDuplicates } from './meta/util'
const actionType = 'AddInstalledAppsAction'
export class AddInstalledAppsAction extends AppLogicAction { export class AddInstalledAppsAction extends AppLogicAction {
readonly apps: Array<InstalledApp> readonly apps: Array<InstalledApp>
@@ -25,7 +27,7 @@ export class AddInstalledAppsAction extends AppLogicAction {
constructor ({ apps }: {apps: Array<InstalledApp>}) { constructor ({ apps }: {apps: Array<InstalledApp>}) {
super() super()
assertNonEmptyListWithoutDuplicates(apps.map((app) => app.packageName)) assertNonEmptyListWithoutDuplicates({ actionType, field: 'apps', list: apps.map((app) => app.packageName) })
this.apps = apps this.apps = apps
} }
+20 -5
View File
@@ -15,8 +15,11 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { assertIdWithinFamily } from '../util/token'
import { AppLogicAction } from './basetypes' import { AppLogicAction } from './basetypes'
import { InvalidActionParameterException } from './meta/exception'
import { assertIdWithinFamily } from './meta/util'
const actionType = 'AddUsedTimeAction'
export class AddUsedTimeAction extends AppLogicAction { export class AddUsedTimeAction extends AppLogicAction {
readonly categoryId: string readonly categoryId: string
@@ -32,18 +35,30 @@ export class AddUsedTimeAction extends AppLogicAction {
}) { }) {
super() super()
assertIdWithinFamily(categoryId) assertIdWithinFamily({ actionType, field: 'categoryId', value: categoryId })
if (dayOfEpoch < 0 || (!Number.isSafeInteger(dayOfEpoch))) { if (dayOfEpoch < 0 || (!Number.isSafeInteger(dayOfEpoch))) {
throw new Error('illegal dayOfEpoch') throw new InvalidActionParameterException({
actionType,
staticMessage: 'invalid dayOfEpoch',
dynamicMessage: 'invalid dayOfEpoch: ' + dayOfEpoch
})
} }
if (timeToAdd < 0 || (!Number.isSafeInteger(timeToAdd))) { if (timeToAdd < 0 || (!Number.isSafeInteger(timeToAdd))) {
throw new Error('illegal timeToAdd') throw new InvalidActionParameterException({
actionType,
staticMessage: 'illegal timeToAdd',
dynamicMessage: 'illegal timeToAdd: ' + timeToAdd
})
} }
if (extraTimeToSubtract < 0 || (!Number.isSafeInteger(extraTimeToSubtract))) { if (extraTimeToSubtract < 0 || (!Number.isSafeInteger(extraTimeToSubtract))) {
throw new Error('illegal extra time to subtract') throw new InvalidActionParameterException({
actionType,
staticMessage: 'illegal extra time to subtract',
dynamicMessage: 'illegal extra time to subtract: ' + extraTimeToSubtract
})
} }
this.categoryId = categoryId this.categoryId = categoryId
+47 -40
View File
@@ -15,10 +15,12 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { uniq } from 'lodash'
import { MinuteOfDay } from '../util/minuteofday' import { MinuteOfDay } from '../util/minuteofday'
import { assertIdWithinFamily } from '../util/token'
import { AppLogicAction } from './basetypes' import { AppLogicAction } from './basetypes'
import { InvalidActionParameterException } from './meta/exception'
import { assertIdWithinFamily, assertListWithoutDuplicates, assertSafeInteger, throwOutOfRange } from './meta/util'
const actionType = 'AddUsedTimeActionVersion2'
export class AddUsedTimeActionVersion2 extends AppLogicAction { export class AddUsedTimeActionVersion2 extends AppLogicAction {
readonly dayOfEpoch: number readonly dayOfEpoch: number
@@ -44,46 +46,54 @@ export class AddUsedTimeActionVersion2 extends AppLogicAction {
}) { }) {
super() super()
if (dayOfEpoch < 0 || (!Number.isSafeInteger(dayOfEpoch))) { assertSafeInteger({ actionType, field: 'dayOfEpoch', value: dayOfEpoch })
throw new Error('illegal dayOfEpoch')
if (dayOfEpoch < 0) {
throwOutOfRange({ actionType, field: 'dayOfEpoch', value: dayOfEpoch })
} }
if (trustedTimestamp < 0 || (!Number.isSafeInteger(trustedTimestamp))) { assertSafeInteger({ actionType, field: 'trustedTimestamp', value: trustedTimestamp })
throw new Error('illegal trustedTimestamp')
if (trustedTimestamp < 0) {
throwOutOfRange({ actionType, field: 'trustedTimestamp', value: trustedTimestamp })
} }
if (items.length === 0) { if (items.length === 0) {
throw new Error('missing items') throw new InvalidActionParameterException({ actionType, staticMessage: 'no items' })
} }
if (items.length !== uniq(items.map((item) => item.categoryId)).length) { assertListWithoutDuplicates({
throw new Error('duplicate category ids') actionType,
} field: 'categoryIds',
list: items.map((item) => item.categoryId)
})
items.forEach((item) => { items.forEach((item) => {
assertIdWithinFamily(item.categoryId) assertIdWithinFamily({ actionType, field: 'categoryId', value: item.categoryId })
if (item.timeToAdd < 0 || (!Number.isSafeInteger(item.timeToAdd))) { assertSafeInteger({ actionType, field: 'timeToAdd', value: item.timeToAdd })
throw new Error('illegal timeToAdd')
if (item.timeToAdd < 0) {
throwOutOfRange({ actionType, field: 'timeToAdd', value: item.timeToAdd })
} }
if (item.extraTimeToSubtract < 0 || (!Number.isSafeInteger(item.extraTimeToSubtract))) { assertSafeInteger({ actionType, field: 'extraTimeToSubtract', value: item.extraTimeToSubtract })
throw new Error('illegal extra time to subtract')
if (item.extraTimeToSubtract < 0) {
throwOutOfRange({ actionType, field: 'extraTimeToSubtract', value: item.extraTimeToSubtract })
} }
if ( assertListWithoutDuplicates({
uniq(item.additionalCountingSlots.map((item) => JSON.stringify(item.serialize()))).length !== actionType,
item.additionalCountingSlots.length field: 'additionalCountingSlots',
) { list: item.additionalCountingSlots.map((item) => JSON.stringify(item.serialize()))
throw new Error() })
}
if ( assertListWithoutDuplicates({
uniq(item.sessionDurationLimits.map((item) => JSON.stringify(item.serialize()))).length !== actionType,
item.sessionDurationLimits.length field: 'sessionDurationLimits',
) { list: item.sessionDurationLimits.map((item) => JSON.stringify(item.serialize()))
throw new Error() })
}
}) })
this.dayOfEpoch = dayOfEpoch this.dayOfEpoch = dayOfEpoch
@@ -111,16 +121,15 @@ class AddUsedTimeActionItemAdditionalCountingSlot {
readonly end: number readonly end: number
constructor ({ start, end }: { start: number, end: number }) { constructor ({ start, end }: { start: number, end: number }) {
if ((!Number.isSafeInteger(start)) || (!Number.isSafeInteger(end))) { assertSafeInteger({ actionType, field: 'start', value: start })
throw new Error() assertSafeInteger({ actionType, field: 'end', value: end })
}
if (start < MinuteOfDay.MIN || end > MinuteOfDay.MAX || start > end) { if (start < MinuteOfDay.MIN || end > MinuteOfDay.MAX || start > end) {
throw new Error() throw new InvalidActionParameterException({ actionType, staticMessage: 'start or end out of range' })
} }
if (start === MinuteOfDay.MIN && end === MinuteOfDay.MAX) { if (start === MinuteOfDay.MIN && end === MinuteOfDay.MAX) {
throw new Error() throw new InvalidActionParameterException({ actionType, staticMessage: 'couting slot can not fill the whole day' })
} }
this.start = start this.start = start
@@ -139,19 +148,17 @@ class AddUsedTimeActionItemSessionDurationLimitSlot {
readonly pause: number readonly pause: number
constructor ({ start, end, duration, pause }: { start: number, end: number, duration: number, pause: number }) { constructor ({ start, end, duration, pause }: { start: number, end: number, duration: number, pause: number }) {
if ( assertSafeInteger({ actionType, field: 'start', value: start })
(!Number.isSafeInteger(start)) || (!Number.isSafeInteger(end)) || assertSafeInteger({ actionType, field: 'end', value: end })
(!Number.isSafeInteger(duration)) || (!Number.isSafeInteger(pause)) assertSafeInteger({ actionType, field: 'duration', value: duration })
) { assertSafeInteger({ actionType, field: 'pause', value: pause })
throw new Error()
}
if (start < MinuteOfDay.MIN || end > MinuteOfDay.MAX || start > end) { if (start < MinuteOfDay.MIN || end > MinuteOfDay.MAX || start > end) {
throw new Error() throw new InvalidActionParameterException({ actionType, staticMessage: 'start or end out of range' })
} }
if (duration <= 0 || pause <= 0) { if (duration <= 0 || pause <= 0) {
throw new Error() throw new InvalidActionParameterException({ actionType, staticMessage: 'duration and pause must not be zero or smaller' })
} }
this.start = start this.start = start
+20 -5
View File
@@ -15,9 +15,12 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { assertParentPasswordValid, ParentPassword } from '../api/schema' import { assertParentPasswordValid, ParentPassword, ParentPasswordValidationException } from '../api/schema'
import { assertIdWithinFamily } from '../util/token'
import { ParentAction } from './basetypes' import { ParentAction } from './basetypes'
import { InvalidActionParameterException } from './meta/exception'
import { assertIdWithinFamily } from './meta/util'
const actionType = 'AddUserAction'
export class AddUserAction extends ParentAction { export class AddUserAction extends ParentAction {
readonly userId: string readonly userId: string
@@ -35,7 +38,7 @@ export class AddUserAction extends ParentAction {
}) { }) {
super() super()
assertIdWithinFamily(userId) assertIdWithinFamily({ actionType, field: 'userId', value: userId })
this.userId = userId this.userId = userId
this.name = name this.name = name
@@ -45,12 +48,24 @@ export class AddUserAction extends ParentAction {
if (userType === 'parent') { if (userType === 'parent') {
if (!password) { if (!password) {
throw new Error('parent users must have got an password') throw new InvalidActionParameterException({
actionType,
staticMessage: 'parent users must have got an password'
})
} }
} }
if (password) { if (password) {
assertParentPasswordValid(password) try {
assertParentPasswordValid(password)
} catch (ex) {
if (ex instanceof ParentPasswordValidationException) {
throw new InvalidActionParameterException({
actionType,
staticMessage: 'invalid password data'
})
} else throw ex
}
} }
} }
+25 -13
View File
@@ -16,9 +16,11 @@
*/ */
import { createDecipheriv, createHash } from 'crypto' import { createDecipheriv, createHash } from 'crypto'
import { assertIsHexString } from '../util/hexstring'
import { assertIdWithinFamily } from '../util/token'
import { ParentAction } from './basetypes' import { ParentAction } from './basetypes'
import { InvalidActionParameterException } from './meta/exception'
import { assertHexString, assertIdWithinFamily } from './meta/util'
const actionType = 'ChangeParentPasswordAction'
export class ChangeParentPasswordAction extends ParentAction { export class ChangeParentPasswordAction extends ParentAction {
readonly parentUserId: string readonly parentUserId: string
@@ -36,7 +38,7 @@ export class ChangeParentPasswordAction extends ParentAction {
}) { }) {
super() super()
assertIdWithinFamily(parentUserId) assertIdWithinFamily({ actionType, field: 'parentUserId', value: parentUserId })
if ( if (
(!parentUserId) || (!parentUserId) ||
@@ -45,15 +47,25 @@ export class ChangeParentPasswordAction extends ParentAction {
(!newPasswordSecondHashEncrypted) || (!newPasswordSecondHashEncrypted) ||
(!integrity) (!integrity)
) { ) {
throw new Error('missing required parameter for change parent password') throw new InvalidActionParameterException({
actionType,
staticMessage: 'missing required parameter for change parent password'
})
} }
if (integrity.length !== 128) { if (integrity.length !== 128) {
throw new Error('wrong length of integrity data') throw new InvalidActionParameterException({
actionType,
staticMessage: 'wrong length of integrity data'
})
} }
assertIsHexString(newPasswordSecondHashEncrypted) assertHexString({ actionType, field: 'newPasswordSecondHashEncrypted', value: newPasswordSecondHashEncrypted })
assertIsHexString(integrity) assertHexString({ actionType, field: 'integrity', value: integrity })
if (newPasswordSecondHashEncrypted.length <= 70) {
throw new InvalidActionParameterException({ actionType, staticMessage: 'wrong length of the new password' })
}
this.parentUserId = parentUserId this.parentUserId = parentUserId
this.newPasswordFirstHash = newPasswordFirstHash this.newPasswordFirstHash = newPasswordFirstHash
@@ -82,15 +94,11 @@ export class ChangeParentPasswordAction extends ParentAction {
const expected = createHash('sha512').update(integrityData).digest('hex') const expected = createHash('sha512').update(integrityData).digest('hex')
if (expected !== this.integrity) { if (expected !== this.integrity) {
throw new Error('invalid integrity for change parent password action') throw new InvalidChangeParentPasswordIntegrityException()
} }
} }
decryptSecondHash ({ oldPasswordSecondHash }: {oldPasswordSecondHash: string}) { decryptSecondHash ({ oldPasswordSecondHash }: { oldPasswordSecondHash: string }) {
if (this.newPasswordSecondHashEncrypted.length <= 70) {
throw new Error('wrong length of the new password')
}
const ivHex = this.newPasswordSecondHashEncrypted.substring(0, 32) const ivHex = this.newPasswordSecondHashEncrypted.substring(0, 32)
const salt = this.newPasswordSecondHashEncrypted.substring(32, 64) const salt = this.newPasswordSecondHashEncrypted.substring(32, 64)
const encryptedData = this.newPasswordSecondHashEncrypted.substring(64) const encryptedData = this.newPasswordSecondHashEncrypted.substring(64)
@@ -115,3 +123,7 @@ export interface SerializedChangeParentPasswordAction {
secondHashEncrypted: string secondHashEncrypted: string
integrity: string integrity: string
} }
export class InvalidChangeParentPasswordIntegrityException extends Error {
constructor () { super('invalid integrity for change parent password action') }
}
+14 -2
View File
@@ -15,8 +15,11 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { assertParentPasswordValid, ParentPassword } from '../api/schema' import { assertParentPasswordValid, ParentPassword, ParentPasswordValidationException } from '../api/schema'
import { ChildAction } from './basetypes' import { ChildAction } from './basetypes'
import { InvalidActionParameterException } from './meta/exception'
const actionType = 'ChildChangePasswordAction'
export class ChildChangePasswordAction extends ChildAction { export class ChildChangePasswordAction extends ChildAction {
readonly password: ParentPassword readonly password: ParentPassword
@@ -26,7 +29,16 @@ export class ChildChangePasswordAction extends ChildAction {
}) { }) {
super() super()
assertParentPasswordValid(password) try {
assertParentPasswordValid(password)
} catch (ex) {
if (ex instanceof ParentPasswordValidationException) {
throw new InvalidActionParameterException({
actionType,
staticMessage: 'invalid password'
})
} else throw ex
}
this.password = password this.password = password
} }
+1 -1
View File
@@ -22,7 +22,7 @@ export class ChildSignInAction extends ChildAction {
super() super()
} }
static parse = (action: SerializedChildSignInAction) => ( static parse = (_: SerializedChildSignInAction) => (
new ChildSignInAction() new ChildSignInAction()
) )
} }
+5 -3
View File
@@ -15,8 +15,10 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { assertIdWithinFamily } from '../util/token'
import { ParentAction } from './basetypes' import { ParentAction } from './basetypes'
import { assertIdWithinFamily } from './meta/util'
const actionType = 'CreateCategoryAction'
export class CreateCategoryAction extends ParentAction { export class CreateCategoryAction extends ParentAction {
readonly categoryId: string readonly categoryId: string
@@ -26,8 +28,8 @@ export class CreateCategoryAction extends ParentAction {
constructor ({ categoryId, childId, title }: {categoryId: string, childId: string, title: string}) { constructor ({ categoryId, childId, title }: {categoryId: string, childId: string, title: string}) {
super() super()
assertIdWithinFamily(categoryId) assertIdWithinFamily({ actionType, field: 'categoryId', value: categoryId })
assertIdWithinFamily(childId) assertIdWithinFamily({ actionType, field: 'childId', value: childId })
this.categoryId = categoryId this.categoryId = categoryId
this.childId = childId this.childId = childId
+19 -6
View File
@@ -15,8 +15,11 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { SerializedTimeLimitRule, TimelimitRule } from '../model/timelimitrule' import { ParseTimeLimitRuleException, SerializedTimeLimitRule, TimelimitRule } from '../model/timelimitrule'
import { ParentAction } from './basetypes' import { ParentAction } from './basetypes'
import { InvalidActionParameterException } from './meta/exception'
const actionType = 'CreateTimeLimitRuleAction'
export class CreateTimeLimitRuleAction extends ParentAction { export class CreateTimeLimitRuleAction extends ParentAction {
rule: TimelimitRule rule: TimelimitRule
@@ -27,11 +30,21 @@ export class CreateTimeLimitRuleAction extends ParentAction {
this.rule = rule this.rule = rule
} }
static parse = ({ rule }: SerializedCreateTimelimtRuleAction) => ( static parse = ({ rule }: SerializedCreateTimelimtRuleAction) => {
new CreateTimeLimitRuleAction({ try {
rule: TimelimitRule.parse(rule) return new CreateTimeLimitRuleAction({
}) rule: TimelimitRule.parse(rule)
) })
} catch (ex) {
if (ex instanceof ParseTimeLimitRuleException) {
throw new InvalidActionParameterException({
actionType,
staticMessage: 'invalid time limit rule',
dynamicMessage: 'invalid time limit rule: ' + ex.toString()
})
} else throw ex
}
}
} }
export interface SerializedCreateTimelimtRuleAction { export interface SerializedCreateTimelimtRuleAction {
+5 -3
View File
@@ -15,16 +15,18 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { assertIdWithinFamily } from '../util/token'
import { ParentAction } from './basetypes' import { ParentAction } from './basetypes'
import { assertIdWithinFamily } from './meta/util'
const actionType = 'DeleteCategoryAction'
export class DeleteCategoryAction extends ParentAction { export class DeleteCategoryAction extends ParentAction {
readonly categoryId: string readonly categoryId: string
constructor ({ categoryId }: {categoryId: string}) { constructor ({ categoryId }: { categoryId: string }) {
super() super()
assertIdWithinFamily(categoryId) assertIdWithinFamily({ actionType, field: 'categoryId', value: categoryId })
this.categoryId = categoryId this.categoryId = categoryId
} }
+4 -2
View File
@@ -15,8 +15,10 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { assertIdWithinFamily } from '../util/token'
import { ParentAction } from './basetypes' import { ParentAction } from './basetypes'
import { assertIdWithinFamily } from './meta/util'
const actionType = 'DeleteTimeLimitRuleAction'
export class DeleteTimeLimitRuleAction extends ParentAction { export class DeleteTimeLimitRuleAction extends ParentAction {
readonly ruleId: string readonly ruleId: string
@@ -24,7 +26,7 @@ export class DeleteTimeLimitRuleAction extends ParentAction {
constructor ({ ruleId }: {ruleId: string}) { constructor ({ ruleId }: {ruleId: string}) {
super() super()
assertIdWithinFamily(ruleId) assertIdWithinFamily({ actionType, field: 'ruleId', value: ruleId })
this.ruleId = ruleId this.ruleId = ruleId
} }
+7 -4
View File
@@ -16,8 +16,10 @@
*/ */
import { DeviceHadManipulationFlags } from '../database/device' import { DeviceHadManipulationFlags } from '../database/device'
import { assertIdWithinFamily } from '../util/token'
import { ParentAction } from './basetypes' import { ParentAction } from './basetypes'
import { assertIdWithinFamily, assertSafeInteger, throwOutOfRange } from './meta/util'
const actionType = 'IgnoreManipulationAction'
export class IgnoreManipulationAction extends ParentAction { export class IgnoreManipulationAction extends ParentAction {
readonly deviceId: string readonly deviceId: string
@@ -51,13 +53,14 @@ export class IgnoreManipulationAction extends ParentAction {
}) { }) {
super() super()
assertIdWithinFamily(deviceId) assertIdWithinFamily({ actionType, field: 'deviceId', value: deviceId })
assertSafeInteger({ actionType, field: 'ignoreHadManipulationFlags', value: ignoreHadManipulationFlags })
if ( if (
(!Number.isSafeInteger(ignoreHadManipulationFlags)) ||
ignoreHadManipulationFlags < 0 || ignoreHadManipulationFlags > DeviceHadManipulationFlags.ALL ignoreHadManipulationFlags < 0 || ignoreHadManipulationFlags > DeviceHadManipulationFlags.ALL
) { ) {
throw new Error('invalid ignoreHadManipulationFlags') throwOutOfRange({ actionType, field: 'ignoreHadManipulationFlags', value: ignoreHadManipulationFlags })
} }
this.deviceId = deviceId this.deviceId = deviceId
+12 -6
View File
@@ -15,8 +15,10 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { assertIdWithinFamily } from '../util/token'
import { ParentAction } from './basetypes' import { ParentAction } from './basetypes'
import { assertIdWithinFamily, assertSafeInteger, throwOutOfRange } from './meta/util'
const actionType = 'IncrementCategoryExtraTimeAction'
export class IncrementCategoryExtraTimeAction extends ParentAction { export class IncrementCategoryExtraTimeAction extends ParentAction {
readonly categoryId: string readonly categoryId: string
@@ -26,14 +28,18 @@ export class IncrementCategoryExtraTimeAction extends ParentAction {
constructor ({ categoryId, addedExtraTime, day }: {categoryId: string, addedExtraTime: number, day: number}) { constructor ({ categoryId, addedExtraTime, day }: {categoryId: string, addedExtraTime: number, day: number}) {
super() super()
assertIdWithinFamily(categoryId) assertIdWithinFamily({ actionType, field: 'categoryId', value: categoryId })
if (addedExtraTime <= 0 || (!Number.isSafeInteger(addedExtraTime))) { assertSafeInteger({ actionType, field: 'addedExtraTime', value: addedExtraTime })
throw new Error('must add some extra time with IncrementCategoryExtraTimeAction')
if (addedExtraTime < 0) {
throwOutOfRange({ actionType, field: 'addedExtraTime', value: addedExtraTime })
} }
if (day < -1 || (!Number.isSafeInteger(day))) { assertSafeInteger({ actionType, field: 'day', value: day })
throw Error('day must be valid')
if (day < -1) {
throwOutOfRange({ actionType, field: 'day', value: day })
} }
this.categoryId = categoryId this.categoryId = categoryId
+40
View File
@@ -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/>.
*/
import { StaticMessageException } from '../../exception'
export class InvalidActionParameterException extends StaticMessageException {
constructor ({ actionType, staticMessage, dynamicMessage }: {
actionType: string
staticMessage: string
dynamicMessage?: string
}) {
super({
staticMessage: 'invalid action paramters:' + actionType + ':' + staticMessage,
dynamicMessage: dynamicMessage ? 'invalid action paramters:' + actionType + ':' + dynamicMessage : undefined
})
}
}
export class UnknownActionTypeException extends InvalidActionParameterException {
constructor ({ group }: { group: string }) {
super({
actionType: group,
staticMessage: 'unknown action type'
})
}
}
+104
View File
@@ -0,0 +1,104 @@
/*
* 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 { checkIfHexString } from '../../util/hexstring'
import { hasDuplicates } from '../../util/list'
import { isIdWithinFamily } from '../../util/token'
import { InvalidActionParameterException } from './exception'
export const assertIdWithinFamily = ({ value, actionType, field }: {
value: string
actionType: string
field: string
}) => {
if (!isIdWithinFamily(value)) {
throw new InvalidActionParameterException({
actionType,
staticMessage: 'invalid id within family for ' + field,
dynamicMessage: 'invalid id within family for ' + field + ': ' + value
})
}
}
export const assertHexString = ({ value, actionType, field }: {
value: string
actionType: string
field: string
}) => {
if (!checkIfHexString(value)) {
throw new InvalidActionParameterException({
actionType,
staticMessage: 'invalid hex string for ' + field,
dynamicMessage: 'invalid hex string for ' + field + ': ' + value
})
}
}
export const assertSafeInteger = ({ value, actionType, field }: {
value: number
actionType: string
field: string
}) => {
if (!Number.isSafeInteger(value)) {
throw new InvalidActionParameterException({
actionType,
staticMessage: 'require number for ' + field,
dynamicMessage: 'require number for ' + field + ': ' + value
})
}
}
export const throwOutOfRange = ({ value, actionType, field }: {
value: number
actionType: string
field: string
}) => {
throw new InvalidActionParameterException({
actionType,
staticMessage: field + ' out of range',
dynamicMessage: field + ' out of range: ' + value
})
}
export function assertNonEmptyListWithoutDuplicates ({ list, actionType, field }: {
list: Array<string>
actionType: string
field: string
}) {
assertListWithoutDuplicates({ list, actionType, field })
if (hasDuplicates(list)) {
throw new InvalidActionParameterException({
actionType,
staticMessage: 'empty list for ' + field
})
}
}
export function assertListWithoutDuplicates ({ list, actionType, field }: {
list: Array<string>
actionType: string
field: string
}) {
if (hasDuplicates(list)) {
throw new InvalidActionParameterException({
actionType,
staticMessage: 'list has duplicates for ' + field,
dynamicMessage: 'list has duplicates for ' + field + ': ' + list.join(';')
})
}
}
+5 -4
View File
@@ -15,9 +15,10 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { assertNonEmptyListWithoutDuplicates } from '../util/list'
import { assertIdWithinFamily } from '../util/token'
import { ParentAction } from './basetypes' import { ParentAction } from './basetypes'
import { assertIdWithinFamily, assertNonEmptyListWithoutDuplicates } from './meta/util'
const actionType = 'RemoveCategoryAppsAction'
export class RemoveCategoryAppsAction extends ParentAction { export class RemoveCategoryAppsAction extends ParentAction {
readonly categoryId: string readonly categoryId: string
@@ -26,8 +27,8 @@ export class RemoveCategoryAppsAction extends ParentAction {
constructor ({ categoryId, packageNames }: {categoryId: string, packageNames: Array<string>}) { constructor ({ categoryId, packageNames }: {categoryId: string, packageNames: Array<string>}) {
super() super()
assertIdWithinFamily(categoryId) assertIdWithinFamily({ actionType, field: 'categoryId', value: categoryId })
assertNonEmptyListWithoutDuplicates(packageNames) assertNonEmptyListWithoutDuplicates({ actionType, field: 'packageNames', list: packageNames })
this.categoryId = categoryId this.categoryId = categoryId
this.packageNames = packageNames this.packageNames = packageNames
+4 -2
View File
@@ -15,8 +15,10 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { assertNonEmptyListWithoutDuplicates } from '../util/list'
import { AppLogicAction } from './basetypes' import { AppLogicAction } from './basetypes'
import { assertNonEmptyListWithoutDuplicates } from './meta/util'
const actionType = 'RemoveInstalledAppsAction'
export class RemoveInstalledAppsAction extends AppLogicAction { export class RemoveInstalledAppsAction extends AppLogicAction {
readonly packageNames: Array<string> readonly packageNames: Array<string>
@@ -24,7 +26,7 @@ export class RemoveInstalledAppsAction extends AppLogicAction {
constructor ({ packageNames }: {packageNames: Array<string>}) { constructor ({ packageNames }: {packageNames: Array<string>}) {
super() super()
assertNonEmptyListWithoutDuplicates(packageNames) assertNonEmptyListWithoutDuplicates({ actionType, field: 'packageNames', list: packageNames })
this.packageNames = packageNames this.packageNames = packageNames
} }
+4 -2
View File
@@ -15,8 +15,10 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { assertIdWithinFamily } from '../util/token'
import { ParentAction } from './basetypes' import { ParentAction } from './basetypes'
import { assertIdWithinFamily } from './meta/util'
const actionType = 'RemoveUserAction'
export class RemoveUserAction extends ParentAction { export class RemoveUserAction extends ParentAction {
readonly userId: string readonly userId: string
@@ -31,7 +33,7 @@ export class RemoveUserAction extends ParentAction {
}) { }) {
super() super()
assertIdWithinFamily(userId) assertIdWithinFamily({ actionType, field: 'userId', value: userId })
this.userId = userId this.userId = userId
this.authentication = authentication this.authentication = authentication
+9 -3
View File
@@ -15,8 +15,11 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { assertIdWithinFamily } from '../util/token'
import { ParentAction } from './basetypes' import { ParentAction } from './basetypes'
import { InvalidActionParameterException } from './meta/exception'
import { assertIdWithinFamily } from './meta/util'
const actionType = 'RenameChildAction'
export class RenameChildAction extends ParentAction { export class RenameChildAction extends ParentAction {
readonly childId: string readonly childId: string
@@ -28,10 +31,13 @@ export class RenameChildAction extends ParentAction {
}) { }) {
super() super()
assertIdWithinFamily(childId) assertIdWithinFamily({ actionType, field: 'childId', value: childId })
if (newName === '') { if (newName === '') {
throw new Error('new name must not be empty') throw new InvalidActionParameterException({
actionType,
staticMessage: 'new name must not be empty'
})
} }
this.childId = childId this.childId = childId
+4 -2
View File
@@ -15,8 +15,10 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { assertIdWithinFamily } from '../util/token'
import { ParentAction } from './basetypes' import { ParentAction } from './basetypes'
import { assertIdWithinFamily } from './meta/util'
const actionType = 'ResetCategoryNetworkIdsAction'
export class ResetCategoryNetworkIdsAction extends ParentAction { export class ResetCategoryNetworkIdsAction extends ParentAction {
readonly categoryId: string readonly categoryId: string
@@ -26,7 +28,7 @@ export class ResetCategoryNetworkIdsAction extends ParentAction {
}) { }) {
super() super()
assertIdWithinFamily(categoryId) assertIdWithinFamily({ actionType, field: 'categoryId', value: categoryId })
this.categoryId = categoryId this.categoryId = categoryId
} }
+4 -2
View File
@@ -15,8 +15,10 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { assertIdWithinFamily } from '../util/token'
import { ParentAction } from './basetypes' import { ParentAction } from './basetypes'
import { assertIdWithinFamily } from './meta/util'
const actionType = 'ResetParentBlockedTimesAction'
export class ResetParentBlockedTimesAction extends ParentAction { export class ResetParentBlockedTimesAction extends ParentAction {
readonly parentId: string readonly parentId: string
@@ -26,7 +28,7 @@ export class ResetParentBlockedTimesAction extends ParentAction {
}) { }) {
super() super()
assertIdWithinFamily(parentId) assertIdWithinFamily({ actionType, field: 'parentId', value: parentId })
this.parentId = parentId this.parentId = parentId
} }
+2 -1
View File
@@ -20,6 +20,7 @@ import { AddUsedTimeAction, SerializedAddUsedTimeAction } from '../addusedtime'
import { AddUsedTimeActionVersion2, SerializedAddUsedTimeActionVersion2 } from '../addusedtime2' import { AddUsedTimeActionVersion2, SerializedAddUsedTimeActionVersion2 } from '../addusedtime2'
import { AppLogicAction } from '../basetypes' import { AppLogicAction } from '../basetypes'
import { ForceSyncAction, SerializedForceSyncAction } from '../forcesync' import { ForceSyncAction, SerializedForceSyncAction } from '../forcesync'
import { UnknownActionTypeException } from '../meta/exception'
import { RemoveInstalledAppsAction, SerializedRemoveInstalledAppsAction } from '../removeinstalledapps' import { RemoveInstalledAppsAction, SerializedRemoveInstalledAppsAction } from '../removeinstalledapps'
import { SerializedSignOutAtDeviceAction, SignOutAtDeviceAction } from '../signoutatdevice' import { SerializedSignOutAtDeviceAction, SignOutAtDeviceAction } from '../signoutatdevice'
import { SerialiezdTriedDisablingDeviceAdminAction, TriedDisablingDeviceAdminAction } from '../trieddisablingdeviceadmin' import { SerialiezdTriedDisablingDeviceAdminAction, TriedDisablingDeviceAdminAction } from '../trieddisablingdeviceadmin'
@@ -57,6 +58,6 @@ export const parseAppLogicAction = (serialized: SerializedAppLogicAction): AppLo
} else if (serialized.type === 'UPDATE_DEVICE_STATUS') { } else if (serialized.type === 'UPDATE_DEVICE_STATUS') {
return UpdateDeviceStatusAction.parse(serialized) return UpdateDeviceStatusAction.parse(serialized)
} else { } else {
throw new Error('illegal state: unsupported type at parseAppLogicAction') throw new UnknownActionTypeException({ group: 'app logic' })
} }
} }
+3 -2
View File
@@ -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
@@ -17,6 +17,7 @@
import { ChildChangePasswordAction, SerializedChildChangePasswordAction } from '../childchangepassword' import { ChildChangePasswordAction, SerializedChildChangePasswordAction } from '../childchangepassword'
import { ChildSignInAction, SerializedChildSignInAction } from '../childsignin' import { ChildSignInAction, SerializedChildSignInAction } from '../childsignin'
import { UnknownActionTypeException } from '../meta/exception'
export type SerializedChildAction = SerializedChildChangePasswordAction | SerializedChildSignInAction export type SerializedChildAction = SerializedChildChangePasswordAction | SerializedChildSignInAction
@@ -26,6 +27,6 @@ export const parseChildAction = (serialized: SerializedChildAction) => {
} else if (serialized.type === 'CHILD_SIGN_IN') { } else if (serialized.type === 'CHILD_SIGN_IN') {
return ChildSignInAction.parse(serialized) return ChildSignInAction.parse(serialized)
} else { } else {
throw new Error('illegal state: unsupported type at parseChildAction') throw new UnknownActionTypeException({ group: 'child' })
} }
} }
+2 -1
View File
@@ -26,6 +26,7 @@ import { DeleteCategoryAction, SerializedDeleteCategoryAction } from '../deletec
import { DeleteTimeLimitRuleAction, SerializedDeleteTimeLimitRuleAction } from '../deletetimelimitrule' import { DeleteTimeLimitRuleAction, SerializedDeleteTimeLimitRuleAction } from '../deletetimelimitrule'
import { IgnoreManipulationAction, SerializedIgnoreManipulationAction } from '../ignoremanipulation' import { IgnoreManipulationAction, SerializedIgnoreManipulationAction } from '../ignoremanipulation'
import { IncrementCategoryExtraTimeAction, SerializedIncrementCategoryExtraTimeAction } from '../incrementcategoryextratime' import { IncrementCategoryExtraTimeAction, SerializedIncrementCategoryExtraTimeAction } from '../incrementcategoryextratime'
import { UnknownActionTypeException } from '../meta/exception'
import { RemoveCategoryAppsAction, SerializedRemoveCategoryAppsAction } from '../removecategoryapps' import { RemoveCategoryAppsAction, SerializedRemoveCategoryAppsAction } from '../removecategoryapps'
import { RemoveUserAction, SerializedRemoveUserAction } from '../removeuser' import { RemoveUserAction, SerializedRemoveUserAction } from '../removeuser'
import { RenameChildAction, SerializedRenameChildAction } from '../renamechild' import { RenameChildAction, SerializedRenameChildAction } from '../renamechild'
@@ -193,6 +194,6 @@ export const parseParentAction = (action: SerializedParentAction): ParentAction
} else if (action.type === 'UPDATE_USER_LIMIT_LOGIN_CATEGORY') { } else if (action.type === 'UPDATE_USER_LIMIT_LOGIN_CATEGORY') {
return UpdateUserLimitLoginCategory.parse(action) return UpdateUserLimitLoginCategory.parse(action)
} else { } else {
throw new Error('illegal state: invalid type for action at parseParentAction') throw new UnknownActionTypeException({ group: 'parent' })
} }
} }
+12 -6
View File
@@ -15,8 +15,10 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { assertIdWithinFamily } from '../util/token'
import { ParentAction } from './basetypes' import { ParentAction } from './basetypes'
import { assertIdWithinFamily, assertSafeInteger, throwOutOfRange } from './meta/util'
const actionType = 'SetCategoryExtraTimeAction'
export class SetCategoryExtraTimeAction extends ParentAction { export class SetCategoryExtraTimeAction extends ParentAction {
readonly categoryId: string readonly categoryId: string
@@ -26,14 +28,18 @@ export class SetCategoryExtraTimeAction extends ParentAction {
constructor ({ categoryId, newExtraTime, day }: {categoryId: string, newExtraTime: number, day: number}) { constructor ({ categoryId, newExtraTime, day }: {categoryId: string, newExtraTime: number, day: number}) {
super() super()
assertIdWithinFamily(categoryId) assertIdWithinFamily({ actionType, field: 'categoryId', value: categoryId })
if (newExtraTime < 0 || (!Number.isSafeInteger(newExtraTime))) { assertSafeInteger({ actionType, field: 'newExtraTime', value: newExtraTime })
throw Error('newExtraTime must be >= 0')
if (newExtraTime < 0) {
throwOutOfRange({ actionType, field: 'newExtraTime', value: newExtraTime })
} }
if (day < -1 || (!Number.isSafeInteger(day))) { assertSafeInteger({ actionType, field: 'day', value: day })
throw Error('day must be valid')
if (day < -1) {
throwOutOfRange({ actionType, field: 'day', value: day })
} }
this.categoryId = categoryId this.categoryId = categoryId
+5 -3
View File
@@ -15,8 +15,10 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { assertIdWithinFamily } from '../util/token'
import { ParentAction } from './basetypes' import { ParentAction } from './basetypes'
import { assertIdWithinFamily } from './meta/util'
const actionType = 'SetCategoryForUnassignedAppsAction'
export class SetCategoryForUnassignedAppsAction extends ParentAction { export class SetCategoryForUnassignedAppsAction extends ParentAction {
readonly childId: string readonly childId: string
@@ -28,10 +30,10 @@ export class SetCategoryForUnassignedAppsAction extends ParentAction {
}) { }) {
super() super()
assertIdWithinFamily(childId) assertIdWithinFamily({ actionType, field: 'childId', value: childId })
if (categoryId !== '') { if (categoryId !== '') {
assertIdWithinFamily(categoryId) assertIdWithinFamily({ actionType, field: 'categoryId', value: categoryId })
} }
this.childId = childId this.childId = childId
+17 -4
View File
@@ -15,9 +15,12 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { assertParentPasswordValid, ParentPassword } from '../api/schema' import { assertParentPasswordValid, ParentPassword, ParentPasswordValidationException } from '../api/schema'
import { assertIdWithinFamily } from '../util/token'
import { ParentAction } from './basetypes' import { ParentAction } from './basetypes'
import { InvalidActionParameterException } from './meta/exception'
import { assertIdWithinFamily } from './meta/util'
const actionType = 'SetChildPasswordAction'
export class SetChildPasswordAction extends ParentAction { export class SetChildPasswordAction extends ParentAction {
readonly childUserId: string readonly childUserId: string
@@ -29,8 +32,18 @@ export class SetChildPasswordAction extends ParentAction {
}) { }) {
super() super()
assertIdWithinFamily(childUserId) assertIdWithinFamily({ actionType, field: 'childUserId', value: childUserId })
assertParentPasswordValid(newPassword)
try {
assertParentPasswordValid(newPassword)
} catch (ex) {
if (ex instanceof ParentPasswordValidationException) {
throw new InvalidActionParameterException({
actionType,
staticMessage: 'invalid parent password'
})
} else throw ex
}
this.childUserId = childUserId this.childUserId = childUserId
this.newPassword = newPassword this.newPassword = newPassword
+4 -2
View File
@@ -15,8 +15,10 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { assertIdWithinFamily } from '../util/token'
import { ParentAction } from './basetypes' import { ParentAction } from './basetypes'
import { assertIdWithinFamily } from './meta/util'
const actionType = 'SetConsiderRebootManipulationAction'
export class SetConsiderRebootManipulationAction extends ParentAction { export class SetConsiderRebootManipulationAction extends ParentAction {
readonly deviceId: string readonly deviceId: string
@@ -25,7 +27,7 @@ export class SetConsiderRebootManipulationAction extends ParentAction {
constructor ({ deviceId, enable }: {deviceId: string, enable: boolean}) { constructor ({ deviceId, enable }: {deviceId: string, enable: boolean}) {
super() super()
assertIdWithinFamily(deviceId) assertIdWithinFamily({ actionType, field: 'deviceId', value: deviceId })
this.deviceId = deviceId this.deviceId = deviceId
this.enable = enable this.enable = enable
+5 -3
View File
@@ -15,8 +15,10 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { assertIdWithinFamily } from '../util/token'
import { ParentAction } from './basetypes' import { ParentAction } from './basetypes'
import { assertIdWithinFamily } from './meta/util'
const actionType = 'SetDeviceDefaultUserAction'
export class SetDeviceDefaultUserAction extends ParentAction { export class SetDeviceDefaultUserAction extends ParentAction {
readonly deviceId: string readonly deviceId: string
@@ -28,10 +30,10 @@ export class SetDeviceDefaultUserAction extends ParentAction {
}) { }) {
super() super()
assertIdWithinFamily(deviceId) assertIdWithinFamily({ actionType, field: 'deviceId', value: deviceId })
if (defaultUserId !== '') { if (defaultUserId !== '') {
assertIdWithinFamily(defaultUserId) assertIdWithinFamily({ actionType, field: 'defaultUserId', value: defaultUserId })
} }
this.deviceId = deviceId this.deviceId = deviceId
+12 -4
View File
@@ -15,8 +15,11 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { assertIdWithinFamily } from '../util/token'
import { ParentAction } from './basetypes' import { ParentAction } from './basetypes'
import { InvalidActionParameterException } from './meta/exception'
import { assertIdWithinFamily, assertSafeInteger } from './meta/util'
const actionType = 'SetDeviceDefaultUserTimeoutAction'
export class SetDeviceDefaultUserTimeoutAction extends ParentAction { export class SetDeviceDefaultUserTimeoutAction extends ParentAction {
readonly deviceId: string readonly deviceId: string
@@ -28,10 +31,15 @@ export class SetDeviceDefaultUserTimeoutAction extends ParentAction {
}) { }) {
super() super()
assertIdWithinFamily(deviceId) assertIdWithinFamily({ actionType, field: 'deviceId', value: deviceId })
assertSafeInteger({ actionType, field: 'timeout', value: timeout })
if ((!Number.isInteger(timeout)) || (timeout < 0)) { if (timeout < 0) {
throw new Error('timeout must be a non-negative integer') throw new InvalidActionParameterException({
actionType,
staticMessage: 'timeout must be a non-negative integer',
dynamicMessage: 'timeout must be a non-negative integer, was ' + timeout
})
} }
this.deviceId = deviceId this.deviceId = deviceId
+5 -3
View File
@@ -15,8 +15,10 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { assertIdWithinFamily } from '../util/token'
import { ParentAction } from './basetypes' import { ParentAction } from './basetypes'
import { assertIdWithinFamily } from './meta/util'
const actionType = 'SetDeviceUserAction'
export class SetDeviceUserAction extends ParentAction { export class SetDeviceUserAction extends ParentAction {
readonly deviceId: string readonly deviceId: string
@@ -28,10 +30,10 @@ export class SetDeviceUserAction extends ParentAction {
}) { }) {
super() super()
assertIdWithinFamily(deviceId) assertIdWithinFamily({ actionType, field: 'deviceId', value: deviceId })
if (userId !== '') { if (userId !== '') {
assertIdWithinFamily(userId) assertIdWithinFamily({ actionType, field: 'userId', value: userId })
} }
this.deviceId = deviceId this.deviceId = deviceId
+4 -2
View File
@@ -15,8 +15,10 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { assertIdWithinFamily } from '../util/token'
import { ParentAction } from './basetypes' import { ParentAction } from './basetypes'
import { assertIdWithinFamily } from './meta/util'
const actionType = 'SetKeepSignedInAction'
export class SetKeepSignedInAction extends ParentAction { export class SetKeepSignedInAction extends ParentAction {
readonly deviceId: string readonly deviceId: string
@@ -28,7 +30,7 @@ export class SetKeepSignedInAction extends ParentAction {
}) { }) {
super() super()
assertIdWithinFamily(deviceId) assertIdWithinFamily({ actionType, field: 'deviceId', value: deviceId })
this.deviceId = deviceId this.deviceId = deviceId
this.keepSignedIn = keepSignedIn this.keepSignedIn = keepSignedIn
+5 -3
View File
@@ -15,8 +15,10 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { assertIdWithinFamily } from '../util/token'
import { ParentAction } from './basetypes' import { ParentAction } from './basetypes'
import { assertIdWithinFamily } from './meta/util'
const actionType = 'SetParentCategoryAction'
export class SetParentCategoryAction extends ParentAction { export class SetParentCategoryAction extends ParentAction {
readonly categoryId: string readonly categoryId: string
@@ -28,10 +30,10 @@ export class SetParentCategoryAction extends ParentAction {
}) { }) {
super() super()
assertIdWithinFamily(categoryId) assertIdWithinFamily({ actionType, field: 'categoryId', value: categoryId })
if (parentCategory !== '') { if (parentCategory !== '') {
assertIdWithinFamily(parentCategory) assertIdWithinFamily({ actionType, field: 'parentCategory', value: parentCategory })
} }
this.categoryId = categoryId this.categoryId = categoryId
+4 -2
View File
@@ -15,8 +15,10 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { assertIdWithinFamily } from '../util/token'
import { ParentAction } from './basetypes' import { ParentAction } from './basetypes'
import { assertIdWithinFamily } from './meta/util'
const actionType = 'SetRelaxPrimaryDeviceAction'
export class SetRelaxPrimaryDeviceAction extends ParentAction { export class SetRelaxPrimaryDeviceAction extends ParentAction {
readonly userId: string readonly userId: string
@@ -28,7 +30,7 @@ export class SetRelaxPrimaryDeviceAction extends ParentAction {
}) { }) {
super() super()
assertIdWithinFamily(userId) assertIdWithinFamily({ actionType, field: 'userId', value: userId })
this.userId = userId this.userId = userId
this.relax = relax this.relax = relax
+4 -2
View File
@@ -15,8 +15,10 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { assertIdWithinFamily } from '../util/token'
import { ParentAction } from './basetypes' import { ParentAction } from './basetypes'
import { assertIdWithinFamily } from './meta/util'
const actionType = 'SetSendDeviceConnected'
export class SetSendDeviceConnected extends ParentAction { export class SetSendDeviceConnected extends ParentAction {
readonly deviceId: string readonly deviceId: string
@@ -28,7 +30,7 @@ export class SetSendDeviceConnected extends ParentAction {
}) { }) {
super() super()
assertIdWithinFamily(deviceId) assertIdWithinFamily({ actionType, field: 'deviceId', value: deviceId })
this.deviceId = deviceId this.deviceId = deviceId
this.enable = enable this.enable = enable
+12 -4
View File
@@ -15,8 +15,11 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { assertIdWithinFamily } from '../util/token'
import { ParentAction } from './basetypes' import { ParentAction } from './basetypes'
import { InvalidActionParameterException } from './meta/exception'
import { assertIdWithinFamily, assertSafeInteger } from './meta/util'
const actionType = 'SetUserDisableLimitsUntilAction'
export class SetUserDisableLimitsUntilAction extends ParentAction { export class SetUserDisableLimitsUntilAction extends ParentAction {
readonly childId: string readonly childId: string
@@ -28,10 +31,15 @@ export class SetUserDisableLimitsUntilAction extends ParentAction {
}) { }) {
super() super()
assertIdWithinFamily(childId) assertIdWithinFamily({ actionType, field: 'childId', value: childId })
assertSafeInteger({ actionType, field: 'timestamp', value: timestamp })
if (timestamp < 0 || (!Number.isSafeInteger(timestamp))) { if (timestamp < 0) {
throw new Error('timestamp for set user disabe limits until must be >= 0') throw new InvalidActionParameterException({
actionType,
staticMessage: 'timestamp for set user disabe limits until must be >= 0',
dynamicMessage: 'timestamp for set user disabe limits until must be >= 0, but was ' + timestamp
})
} }
this.childId = childId this.childId = childId
+4 -2
View File
@@ -15,8 +15,10 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { assertIdWithinFamily } from '../util/token'
import { ParentAction } from './basetypes' import { ParentAction } from './basetypes'
import { assertIdWithinFamily } from './meta/util'
const actionType = 'SetUserTimezoneAction'
export class SetUserTimezoneAction extends ParentAction { export class SetUserTimezoneAction extends ParentAction {
readonly userId: string readonly userId: string
@@ -28,7 +30,7 @@ export class SetUserTimezoneAction extends ParentAction {
}) { }) {
super() super()
assertIdWithinFamily(userId) assertIdWithinFamily({ actionType, field: 'userId', value: userId })
this.userId = userId this.userId = userId
this.timezone = timezone this.timezone = timezone
+1 -1
View File
@@ -24,7 +24,7 @@ export class SignOutAtDeviceAction extends AppLogicAction {
super() super()
} }
static parse = (action: SerializedSignOutAtDeviceAction) => SignOutAtDeviceAction.instance static parse = (_: SerializedSignOutAtDeviceAction) => SignOutAtDeviceAction.instance
} }
export interface SerializedSignOutAtDeviceAction { export interface SerializedSignOutAtDeviceAction {
+37 -11
View File
@@ -15,9 +15,14 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { AppActivityItem, RemovedAppActivityItem, SerializedAppActivityItem, SerializedRemovedAppActivityItem } from '../model/appactivity' import {
import { assertListWithoutDuplicates } from '../util/list' AppActivityItem, IncompleteAppActivityItemException, RemovedAppActivityItem, SerializedAppActivityItem, SerializedRemovedAppActivityItem
} from '../model/appactivity'
import { AppLogicAction } from './basetypes' import { AppLogicAction } from './basetypes'
import { InvalidActionParameterException } from './meta/exception'
import { assertListWithoutDuplicates } from './meta/util'
const actionType = 'UpdateAppActivitiesAction'
export class UpdateAppActivitiesAction extends AppLogicAction { export class UpdateAppActivitiesAction extends AppLogicAction {
readonly removed: Array<RemovedAppActivityItem> readonly removed: Array<RemovedAppActivityItem>
@@ -29,23 +34,44 @@ export class UpdateAppActivitiesAction extends AppLogicAction {
}) { }) {
super() super()
assertListWithoutDuplicates(removed.map((item) => item.packageName + ':' + item.activityName)) assertListWithoutDuplicates({
assertListWithoutDuplicates(updatedOrAdded.map((item) => item.packageName + ':' + item.activityName)) actionType,
field: 'removed',
list: removed.map((item) => item.packageName + ':' + item.activityName)
})
assertListWithoutDuplicates({
actionType,
field: 'updatedOrAdded',
list: updatedOrAdded.map((item) => item.packageName + ':' + item.activityName)
})
if (removed.length === 0 && updatedOrAdded.length === 0) { if (removed.length === 0 && updatedOrAdded.length === 0) {
throw new Error('UpdateAppActivitiesAction is empty') throw new InvalidActionParameterException({
actionType,
staticMessage: 'UpdateAppActivitiesAction is empty'
})
} }
this.removed = removed this.removed = removed
this.updatedOrAdded = updatedOrAdded this.updatedOrAdded = updatedOrAdded
} }
static parse = ({ removed, updatedOrAdded }: SerializedUpdateAppActivitiesAction) => ( static parse = ({ removed, updatedOrAdded }: SerializedUpdateAppActivitiesAction) => {
new UpdateAppActivitiesAction({ try {
removed: removed.map((item) => RemovedAppActivityItem.parse(item)), return new UpdateAppActivitiesAction({
updatedOrAdded: updatedOrAdded.map((item) => AppActivityItem.parse(item)) removed: removed.map((item) => RemovedAppActivityItem.parse(item)),
}) updatedOrAdded: updatedOrAdded.map((item) => AppActivityItem.parse(item))
) })
} catch (ex) {
if (ex instanceof IncompleteAppActivityItemException) {
throw new InvalidActionParameterException({
actionType,
staticMessage: 'invalid app activity item'
})
} else throw ex
}
}
} }
export interface SerializedUpdateAppActivitiesAction { export interface SerializedUpdateAppActivitiesAction {
+12 -6
View File
@@ -15,8 +15,10 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { assertIdWithinFamily } from '../util/token'
import { ParentAction } from './basetypes' import { ParentAction } from './basetypes'
import { assertIdWithinFamily, assertSafeInteger, throwOutOfRange } from './meta/util'
const actionType = 'UpdateCategoryBatteryLimitAction'
export class UpdateCategoryBatteryLimitAction extends ParentAction { export class UpdateCategoryBatteryLimitAction extends ParentAction {
readonly categoryId: string readonly categoryId: string
@@ -30,17 +32,21 @@ export class UpdateCategoryBatteryLimitAction extends ParentAction {
}) { }) {
super() super()
assertIdWithinFamily(categoryId) assertIdWithinFamily({ actionType, field: 'categoryId', value: categoryId })
if (chargeLimit !== undefined) { if (chargeLimit !== undefined) {
if ((!Number.isSafeInteger(chargeLimit)) || chargeLimit < 0 || chargeLimit > 100) { assertSafeInteger({ actionType, field: 'chargeLimit', value: chargeLimit })
throw new Error('charge limit out of range')
if (chargeLimit < 0 || chargeLimit > 100) {
throwOutOfRange({ actionType, field: 'chargeLimit', value: chargeLimit })
} }
} }
if (mobileLimit !== undefined) { if (mobileLimit !== undefined) {
if ((!Number.isSafeInteger(mobileLimit)) || mobileLimit < 0 || mobileLimit > 100) { assertSafeInteger({ actionType, field: 'mobileLimit', value: mobileLimit })
throw new Error('mobile limit out of range')
if (mobileLimit < 0 || mobileLimit > 100) {
throwOutOfRange({ actionType, field: 'mobileLimit', value: mobileLimit })
} }
} }
@@ -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
@@ -15,8 +15,10 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { assertIdWithinFamily } from '../util/token'
import { ParentAction } from './basetypes' import { ParentAction } from './basetypes'
import { assertIdWithinFamily } from './meta/util'
const actionType = 'UpdateCategoryBlockAllNotificationsAction'
export class UpdateCategoryBlockAllNotificationsAction extends ParentAction { export class UpdateCategoryBlockAllNotificationsAction extends ParentAction {
readonly categoryId: string readonly categoryId: string
@@ -25,7 +27,7 @@ export class UpdateCategoryBlockAllNotificationsAction extends ParentAction {
constructor ({ categoryId, blocked }: {categoryId: string, blocked: boolean}) { constructor ({ categoryId, blocked }: {categoryId: string, blocked: boolean}) {
super() super()
assertIdWithinFamily(categoryId) assertIdWithinFamily({ actionType, field: 'categoryId', value: categoryId })
this.categoryId = categoryId this.categoryId = categoryId
this.blocked = blocked this.blocked = blocked
+17 -4
View File
@@ -15,12 +15,15 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { validateBitmask } from '../util/bitmask' import { BitmapValidationException, validateBitmask } from '../util/bitmask'
import { assertIdWithinFamily } from '../util/token'
import { ParentAction } from './basetypes' import { ParentAction } from './basetypes'
import { InvalidActionParameterException } from './meta/exception'
import { assertIdWithinFamily } from './meta/util'
export const blockedTimesBitmaskLength = 60 * 24 * 7 /* number of minutes per week */ export const blockedTimesBitmaskLength = 60 * 24 * 7 /* number of minutes per week */
const actionType = 'UpdateCategoryBlockedTimesAction'
export class UpdateCategoryBlockedTimesAction extends ParentAction { export class UpdateCategoryBlockedTimesAction extends ParentAction {
readonly categoryId: string readonly categoryId: string
readonly blockedTimes: string readonly blockedTimes: string
@@ -31,8 +34,18 @@ export class UpdateCategoryBlockedTimesAction extends ParentAction {
}) { }) {
super() super()
assertIdWithinFamily(categoryId) assertIdWithinFamily({ actionType, field: 'categoryId', value: categoryId })
validateBitmask(blockedTimes, blockedTimesBitmaskLength)
try {
validateBitmask(blockedTimes, blockedTimesBitmaskLength)
} catch (ex) {
if (ex instanceof BitmapValidationException) {
throw new InvalidActionParameterException({
actionType,
staticMessage: 'invalid bitmask'
})
} else throw ex
}
this.categoryId = categoryId this.categoryId = categoryId
this.blockedTimes = blockedTimes this.blockedTimes = blockedTimes
+9 -10
View File
@@ -15,9 +15,10 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { uniq } from 'lodash'
import { assertIdWithinFamily } from '../util/token'
import { ParentAction } from './basetypes' import { ParentAction } from './basetypes'
import { assertIdWithinFamily, assertNonEmptyListWithoutDuplicates } from './meta/util'
const actionType = 'UpdateCategorySortingAction'
export class UpdateCategorySortingAction extends ParentAction { export class UpdateCategorySortingAction extends ParentAction {
readonly categoryIds: Array<string> readonly categoryIds: Array<string>
@@ -27,15 +28,13 @@ export class UpdateCategorySortingAction extends ParentAction {
}) { }) {
super() super()
if (categoryIds.length === 0) { assertNonEmptyListWithoutDuplicates({ actionType, field: 'categoryIds', list: categoryIds })
throw new Error('empty category sorting list')
}
if (uniq(categoryIds).length !== categoryIds.length) { categoryIds.forEach((categoryId) => assertIdWithinFamily({
throw new Error('category sorting list has duplicates') actionType,
} field: 'categoryIds',
value: categoryId
categoryIds.forEach((categoryId) => assertIdWithinFamily(categoryId)) }))
this.categoryIds = categoryIds this.categoryIds = categoryIds
} }
+10 -6
View File
@@ -15,8 +15,11 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { assertIdWithinFamily } from '../util/token'
import { ParentAction } from './basetypes' import { ParentAction } from './basetypes'
import { InvalidActionParameterException } from './meta/exception'
import { assertIdWithinFamily, assertSafeInteger } from './meta/util'
const actionType = 'UpdateCategoryTemporarilyBlockedAction'
export class UpdateCategoryTemporarilyBlockedAction extends ParentAction { export class UpdateCategoryTemporarilyBlockedAction extends ParentAction {
readonly categoryId: string readonly categoryId: string
@@ -30,15 +33,16 @@ export class UpdateCategoryTemporarilyBlockedAction extends ParentAction {
}) { }) {
super() super()
assertIdWithinFamily(categoryId) assertIdWithinFamily({ actionType, field: 'categoryId', value: categoryId })
if (endTime !== undefined) { if (endTime !== undefined) {
if (!Number.isSafeInteger(endTime)) { assertSafeInteger({ actionType, field: 'endTime', value: endTime })
throw new Error()
}
if (!blocked) { if (!blocked) {
throw new Error() throw new InvalidActionParameterException({
actionType,
staticMessage: 'can not set a end time when disabling blocking'
})
} }
} }
+6 -3
View File
@@ -16,8 +16,10 @@
*/ */
import { allowedTimeWarningFlags } from '../database/category' import { allowedTimeWarningFlags } from '../database/category'
import { assertIdWithinFamily } from '../util/token'
import { ParentAction } from './basetypes' import { ParentAction } from './basetypes'
import { assertIdWithinFamily, assertSafeInteger, throwOutOfRange } from './meta/util'
const actionType = 'UpdateCategoryTimeWarningsAction'
export class UpdateCategoryTimeWarningsAction extends ParentAction { export class UpdateCategoryTimeWarningsAction extends ParentAction {
readonly categoryId: string readonly categoryId: string
@@ -31,10 +33,11 @@ export class UpdateCategoryTimeWarningsAction extends ParentAction {
}) { }) {
super() super()
assertIdWithinFamily(categoryId) assertIdWithinFamily({ actionType, field: 'categoryId', value: categoryId })
assertSafeInteger({ actionType, field: 'flags', value: flags })
if ((flags & allowedTimeWarningFlags) !== flags) { if ((flags & allowedTimeWarningFlags) !== flags) {
throw new Error('illegal flags') throwOutOfRange({ actionType, field: 'flags', value: flags })
} }
this.categoryId = categoryId this.categoryId = categoryId
+4 -2
View File
@@ -15,8 +15,10 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { assertIdWithinFamily } from '../util/token'
import { ParentAction } from './basetypes' import { ParentAction } from './basetypes'
import { assertIdWithinFamily } from './meta/util'
const actionType = 'UpdateCategoryTitleAction'
export class UpdateCategoryTitleAction extends ParentAction { export class UpdateCategoryTitleAction extends ParentAction {
readonly categoryId: string readonly categoryId: string
@@ -25,7 +27,7 @@ export class UpdateCategoryTitleAction extends ParentAction {
constructor ({ categoryId, newTitle }: {categoryId: string, newTitle: string}) { constructor ({ categoryId, newTitle }: {categoryId: string, newTitle: string}) {
super() super()
assertIdWithinFamily(categoryId) assertIdWithinFamily({ actionType, field: 'categoryId', value: categoryId })
this.categoryId = categoryId this.categoryId = categoryId
this.newTitle = newTitle this.newTitle = newTitle
+9 -3
View File
@@ -15,8 +15,11 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { assertIdWithinFamily } from '../util/token'
import { ParentAction } from './basetypes' import { ParentAction } from './basetypes'
import { InvalidActionParameterException } from './meta/exception'
import { assertIdWithinFamily } from './meta/util'
const actionType = 'UpdateDeviceNameAction'
export class UpdateDeviceNameAction extends ParentAction { export class UpdateDeviceNameAction extends ParentAction {
readonly deviceId: string readonly deviceId: string
@@ -31,10 +34,13 @@ export class UpdateDeviceNameAction extends ParentAction {
this.deviceId = deviceId this.deviceId = deviceId
this.name = name this.name = name
assertIdWithinFamily(deviceId) assertIdWithinFamily({ actionType, field: 'deviceId', value: deviceId })
if (name.trim().length === 0) { if (name.trim().length === 0) {
throw new Error('new device name must not be blank') throw new InvalidActionParameterException({
actionType,
staticMessage: 'new device name must not be blank'
})
} }
} }
+7 -2
View File
@@ -19,6 +19,9 @@ import { NewPermissionStatus } from '../model/newpermissionstatus'
import { ProtectionLevel } from '../model/protectionlevel' import { ProtectionLevel } from '../model/protectionlevel'
import { RuntimePermissionStatus } from '../model/runtimepermissionstatus' import { RuntimePermissionStatus } from '../model/runtimepermissionstatus'
import { AppLogicAction } from './basetypes' import { AppLogicAction } from './basetypes'
import { assertSafeInteger, throwOutOfRange } from './meta/util'
const actionType = 'UpdateDeviceStatusAction'
export class UpdateDeviceStatusAction extends AppLogicAction { export class UpdateDeviceStatusAction extends AppLogicAction {
readonly newProtetionLevel?: ProtectionLevel readonly newProtetionLevel?: ProtectionLevel
@@ -52,8 +55,10 @@ export class UpdateDeviceStatusAction extends AppLogicAction {
super() super()
if (newAppVersion !== undefined) { if (newAppVersion !== undefined) {
if (!Number.isSafeInteger(newAppVersion) || (newAppVersion < 0)) { assertSafeInteger({ actionType, field: 'newAppVersion', value: newAppVersion })
throw new Error('invalid new ap version')
if (newAppVersion < 0) {
throwOutOfRange({ actionType, field: 'newAppVersion', value: newAppVersion })
} }
} }
@@ -15,8 +15,10 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { assertIdWithinFamily } from '../util/token'
import { ParentAction } from './basetypes' import { ParentAction } from './basetypes'
import { assertIdWithinFamily } from './meta/util'
const actionType = 'UpdateEnableActivityLevelBlockingAction'
export class UpdateEnableActivityLevelBlockingAction extends ParentAction { export class UpdateEnableActivityLevelBlockingAction extends ParentAction {
readonly deviceId: string readonly deviceId: string
@@ -25,7 +27,7 @@ export class UpdateEnableActivityLevelBlockingAction extends ParentAction {
constructor ({ deviceId, enable }: {deviceId: string, enable: boolean}) { constructor ({ deviceId, enable }: {deviceId: string, enable: boolean}) {
super() super()
assertIdWithinFamily(deviceId) assertIdWithinFamily({ actionType, field: 'deviceId', value: deviceId })
this.deviceId = deviceId this.deviceId = deviceId
this.enable = enable this.enable = enable
+4 -2
View File
@@ -15,8 +15,10 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { assertIdWithinFamily } from '../util/token'
import { ParentAction } from './basetypes' import { ParentAction } from './basetypes'
import { assertIdWithinFamily } from './meta/util'
const actionType = 'UpdateNetworkTimeVerificationAction'
export class UpdateNetworkTimeVerificationAction extends ParentAction { export class UpdateNetworkTimeVerificationAction extends ParentAction {
readonly deviceId: string readonly deviceId: string
@@ -28,7 +30,7 @@ export class UpdateNetworkTimeVerificationAction extends ParentAction {
}) { }) {
super() super()
assertIdWithinFamily(deviceId) assertIdWithinFamily({ actionType, field: 'deviceId', value: deviceId })
this.deviceId = deviceId this.deviceId = deviceId
this.mode = mode this.mode = mode
+18 -5
View File
@@ -15,9 +15,12 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { validateAndParseBitmask } from '../util/bitmask' import { BitmapValidationException, validateAndParseBitmask } from '../util/bitmask'
import { assertIdWithinFamily } from '../util/token'
import { ParentAction } from './basetypes' import { ParentAction } from './basetypes'
import { InvalidActionParameterException } from './meta/exception'
import { assertIdWithinFamily } from './meta/util'
const actionType = 'UpdateParentBlockedTimesAction'
export class UpdateParentBlockedTimesAction extends ParentAction { export class UpdateParentBlockedTimesAction extends ParentAction {
readonly parentId: string readonly parentId: string
@@ -29,9 +32,9 @@ export class UpdateParentBlockedTimesAction extends ParentAction {
}) { }) {
super() super()
assertIdWithinFamily(parentId) assertIdWithinFamily({ actionType, field: 'parentId', value: parentId })
{ try {
const parsedBlockedTimes = validateAndParseBitmask(blockedTimes, 60 * 24 * 7 /* number of minutes per week */) const parsedBlockedTimes = validateAndParseBitmask(blockedTimes, 60 * 24 * 7 /* number of minutes per week */)
for (let day = 0; day < 7; day++) { for (let day = 0; day < 7; day++) {
@@ -44,9 +47,19 @@ export class UpdateParentBlockedTimesAction extends ParentAction {
} }
if (blockedMinutes > 60 * 18 /* 18 hours */) { if (blockedMinutes > 60 * 18 /* 18 hours */) {
throw new Error('too much blocked minutes per day') throw new InvalidActionParameterException({
actionType,
staticMessage: 'too much blocked minutes per day'
})
} }
} }
} catch (ex) {
if (ex instanceof BitmapValidationException) {
throw new InvalidActionParameterException({
actionType,
staticMessage: 'invalid bitmask'
})
} else throw ex
} }
this.parentId = parentId this.parentId = parentId
+6 -6
View File
@@ -15,8 +15,10 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { assertIdWithinFamily } from '../util/token'
import { ParentAction } from './basetypes' import { ParentAction } from './basetypes'
import { assertIdWithinFamily, assertSafeInteger, throwOutOfRange } from './meta/util'
const actionType = 'UpdateParentNotificationFlagsAction'
export class UpdateParentNotificationFlagsAction extends ParentAction { export class UpdateParentNotificationFlagsAction extends ParentAction {
readonly parentId: string readonly parentId: string
@@ -30,14 +32,12 @@ export class UpdateParentNotificationFlagsAction extends ParentAction {
}) { }) {
super() super()
assertIdWithinFamily(parentId) assertIdWithinFamily({ actionType, field: 'parentId', value: parentId })
if (!Number.isSafeInteger(flags)) { assertSafeInteger({ actionType, field: 'flags', value: flags })
throw new Error('flags must be an integer')
}
if (flags < 0 || flags > 1) { if (flags < 0 || flags > 1) {
throw new Error('flags are out of the valid range') throwOutOfRange({ actionType, field: 'flags', value: flags })
} }
this.parentId = parentId this.parentId = parentId
+25 -20
View File
@@ -16,8 +16,11 @@
*/ */
import { MinuteOfDay } from '../util/minuteofday' import { MinuteOfDay } from '../util/minuteofday'
import { assertIdWithinFamily } from '../util/token'
import { ParentAction } from './basetypes' import { ParentAction } from './basetypes'
import { InvalidActionParameterException } from './meta/exception'
import { assertIdWithinFamily, assertSafeInteger, throwOutOfRange } from './meta/util'
const actionType = 'UpdateTimelimitRuleAction'
export class UpdateTimelimitRuleAction extends ParentAction { export class UpdateTimelimitRuleAction extends ParentAction {
readonly ruleId: string readonly ruleId: string
@@ -53,35 +56,37 @@ export class UpdateTimelimitRuleAction extends ParentAction {
this.sessionDurationMilliseconds = sessionDurationMilliseconds this.sessionDurationMilliseconds = sessionDurationMilliseconds
this.sessionPauseMilliseconds = sessionPauseMilliseconds this.sessionPauseMilliseconds = sessionPauseMilliseconds
assertIdWithinFamily(ruleId) assertIdWithinFamily({ actionType, field: 'ruleId', value: ruleId })
if (maximumTimeInMillis < 0 || (!Number.isSafeInteger(maximumTimeInMillis))) { assertSafeInteger({ actionType, field: 'maximumTimeInMillis', value: maximumTimeInMillis })
throw new Error('maximumTimeInMillis must be >= 0')
if (maximumTimeInMillis < 0) {
throwOutOfRange({ actionType, field: 'maximumTimeInMillis', value: maximumTimeInMillis })
} }
if (!( assertSafeInteger({ actionType, field: 'dayMask', value: dayMask })
Number.isSafeInteger(dayMask) ||
dayMask < 0 || if (dayMask < 0 || dayMask > (1 | 2 | 4 | 8 | 16 | 32 | 64)) {
dayMask > (1 | 2 | 4 | 8 | 16 | 32 | 64) throwOutOfRange({ actionType, field: 'dayMask', value: dayMask })
)) {
throw new Error('invalid day mask')
} }
if ( assertSafeInteger({ actionType, field: 'start', value: start })
(!Number.isSafeInteger(start)) || assertSafeInteger({ actionType, field: 'end', value: end })
(!Number.isSafeInteger(end)) || assertSafeInteger({ actionType, field: 'sessionDurationMilliseconds', value: sessionDurationMilliseconds })
(!Number.isSafeInteger(sessionDurationMilliseconds)) || assertSafeInteger({ actionType, field: 'sessionPauseMilliseconds', value: sessionPauseMilliseconds })
(!Number.isSafeInteger(sessionPauseMilliseconds))
) {
throw new Error()
}
if (start < MinuteOfDay.MIN || end > MinuteOfDay.MAX || start > end) { if (start < MinuteOfDay.MIN || end > MinuteOfDay.MAX || start > end) {
throw new Error() throw new InvalidActionParameterException({
actionType,
staticMessage: 'time slot out of range'
})
} }
if (sessionDurationMilliseconds < 0 || sessionPauseMilliseconds < 0) { if (sessionDurationMilliseconds < 0 || sessionPauseMilliseconds < 0) {
throw new Error() throw new InvalidActionParameterException({
actionType,
staticMessage: 'session duration lesser than zero'
})
} }
} }
+12 -7
View File
@@ -16,8 +16,11 @@
*/ */
import { UserFlags } from '../model/userflags' import { UserFlags } from '../model/userflags'
import { assertIdWithinFamily } from '../util/token'
import { ParentAction } from './basetypes' import { ParentAction } from './basetypes'
import { InvalidActionParameterException } from './meta/exception'
import { assertIdWithinFamily, assertSafeInteger } from './meta/util'
const actionType = 'UpdateUserFlagsAction'
export class UpdateUserFlagsAction extends ParentAction { export class UpdateUserFlagsAction extends ParentAction {
readonly userId: string readonly userId: string
@@ -31,14 +34,16 @@ export class UpdateUserFlagsAction extends ParentAction {
}) { }) {
super() super()
assertIdWithinFamily(userId) assertIdWithinFamily({ actionType, field: 'userId', value: userId })
assertSafeInteger({ actionType, field: 'modifiedBits', value: modifiedBits })
if ((!Number.isSafeInteger(modifiedBits)) || (!Number.isSafeInteger(newValues))) { assertSafeInteger({ actionType, field: 'newValues', value: newValues })
throw new Error('flags must be an integer')
}
if ((modifiedBits | UserFlags.ALL_FLAGS) !== UserFlags.ALL_FLAGS || (modifiedBits | newValues) !== modifiedBits) { if ((modifiedBits | UserFlags.ALL_FLAGS) !== UserFlags.ALL_FLAGS || (modifiedBits | newValues) !== modifiedBits) {
throw new Error('flags are out of the valid range') throw new InvalidActionParameterException({
actionType,
staticMessage: 'flags are out of the valid range',
dynamicMessage: 'flags are out of the valid range: ' + modifiedBits + ', ' + newValues
})
} }
this.userId = userId this.userId = userId
+5 -3
View File
@@ -15,8 +15,10 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { assertIdWithinFamily } from '../util/token'
import { ParentAction } from './basetypes' import { ParentAction } from './basetypes'
import { assertIdWithinFamily } from './meta/util'
const actionType = 'UpdateUserLimitLoginCategory'
export class UpdateUserLimitLoginCategory extends ParentAction { export class UpdateUserLimitLoginCategory extends ParentAction {
readonly userId: string readonly userId: string
@@ -28,10 +30,10 @@ export class UpdateUserLimitLoginCategory extends ParentAction {
}) { }) {
super() super()
assertIdWithinFamily(userId) assertIdWithinFamily({ actionType, field: 'userId', value: userId })
if (categoryId !== undefined) { if (categoryId !== undefined) {
assertIdWithinFamily(categoryId) assertIdWithinFamily({ actionType, field: 'categoryId', value: categoryId })
} }
this.userId = userId this.userId = userId
+27 -20
View File
@@ -34,9 +34,12 @@ export const createAdminRouter = ({ database, websocket, eventHandler }: {
router.get('/status', async (_, res, next) => { router.get('/status', async (_, res, next) => {
try { try {
const { counters, maxValues } = await eventHandler.getValues()
res.json({ res.json({
websocketClients: websocket.countConnections(), websocketClients: websocket.countConnections(),
counters: await eventHandler.getCounters() counters,
maxValues
}) })
} catch (ex) { } catch (ex) {
next(ex) next(ex)
@@ -45,7 +48,7 @@ export const createAdminRouter = ({ database, websocket, eventHandler }: {
router.post('/reset-counters', async (_, res, next) => { router.post('/reset-counters', async (_, res, next) => {
try { try {
await eventHandler.resetCounters() await eventHandler.reset()
res.json({ ok: true }) res.json({ ok: true })
} catch (ex) { } catch (ex) {
@@ -96,27 +99,31 @@ export const createAdminRouter = ({ database, websocket, eventHandler }: {
throw new BadRequest() throw new BadRequest()
} }
const userEntryUnsafe = await database.user.findOne({ await database.transaction(async (transaction) => {
where: { const userEntryUnsafe = await database.user.findOne({
mail where: {
}, mail
attributes: ['familyId'] },
}) attributes: ['familyId'],
transaction
})
if (!userEntryUnsafe) { if (!userEntryUnsafe) {
throw new Conflict('no user with specified mail address') throw new Conflict('no user with specified mail address')
} }
const userEntry = { const userEntry = {
familyId: userEntryUnsafe.familyId familyId: userEntryUnsafe.familyId
} }
await addPurchase({ await addPurchase({
database, database,
familyId: userEntry.familyId, familyId: userEntry.familyId,
type, type,
transactionId: 'manual-' + type + '-' + generatePurchaseId(), transactionId: 'manual-' + type + '-' + generatePurchaseId(),
websocket websocket,
transaction
})
}) })
res.json({ ok: true }) res.json({ ok: true })
+35 -22
View File
@@ -19,7 +19,7 @@ import { json } from 'body-parser'
import { Router } from 'express' import { Router } from 'express'
import { BadRequest, Forbidden, Unauthorized } from 'http-errors' import { BadRequest, Forbidden, Unauthorized } from 'http-errors'
import { config } from '../config' import { config } from '../config'
import { Database } from '../database' import { Database, Transaction } from '../database'
import { removeDevice } from '../function/device/remove-device' import { removeDevice } from '../function/device/remove-device'
import { canRecoverPassword } from '../function/parent/can-recover-password' import { canRecoverPassword } from '../function/parent/can-recover-password'
import { createAddDeviceToken } from '../function/parent/create-add-device-token' import { createAddDeviceToken } from '../function/parent/create-add-device-token'
@@ -46,7 +46,9 @@ export const createParentRouter = ({ database, websocket }: {database: Database,
} }
const { mailAuthToken } = req.body const { mailAuthToken } = req.body
const { status, mail } = await getStatusByMailToken({ database, mailAuthToken }) const { status, mail } = await database.transaction(async (transaction) => {
return getStatusByMailToken({ database, mailAuthToken, transaction })
})
res.json({ res.json({
status, status,
@@ -148,15 +150,17 @@ export const createParentRouter = ({ database, websocket }: {database: Database,
} }
}) })
async function assertAuthValidAndReturnDeviceEntry ({ deviceAuthToken, parentId, secondPasswordHash }: { async function assertAuthValidAndReturnDeviceEntry ({ deviceAuthToken, parentId, secondPasswordHash, transaction }: {
deviceAuthToken: string deviceAuthToken: string
parentId: string parentId: string
secondPasswordHash: string secondPasswordHash: string
transaction: Transaction
}) { }) {
const deviceEntry = await database.device.findOne({ const deviceEntry = await database.device.findOne({
where: { where: {
deviceAuthToken: deviceAuthToken deviceAuthToken: deviceAuthToken
} },
transaction
}) })
if (!deviceEntry) { if (!deviceEntry) {
@@ -173,7 +177,8 @@ export const createParentRouter = ({ database, websocket }: {database: Database,
familyId: deviceEntry.familyId, familyId: deviceEntry.familyId,
type: 'parent', type: 'parent',
userId: deviceEntry.currentUserId userId: deviceEntry.currentUserId
} },
transaction
}) })
if (!parentEntry) { if (!parentEntry) {
@@ -186,7 +191,8 @@ export const createParentRouter = ({ database, websocket }: {database: Database,
type: 'parent', type: 'parent',
userId: parentId, userId: parentId,
secondPasswordHash: secondPasswordHash secondPasswordHash: secondPasswordHash
} },
transaction
}) })
if (!parentEntry) { if (!parentEntry) {
@@ -203,13 +209,16 @@ export const createParentRouter = ({ database, websocket }: {database: Database,
throw new BadRequest() throw new BadRequest()
} }
const deviceEntry = await assertAuthValidAndReturnDeviceEntry({ const { token, deviceId } = await database.transaction(async (transaction) => {
deviceAuthToken: req.body.deviceAuthToken, const deviceEntry = await assertAuthValidAndReturnDeviceEntry({
parentId: req.body.parentId, deviceAuthToken: req.body.deviceAuthToken,
secondPasswordHash: req.body.parentPasswordSecondHash parentId: req.body.parentId,
}) secondPasswordHash: req.body.parentPasswordSecondHash,
transaction
})
const { token, deviceId } = await createAddDeviceToken({ familyId: deviceEntry.familyId, database }) return createAddDeviceToken({ familyId: deviceEntry.familyId, database, transaction })
})
res.json({ token, deviceId }) res.json({ token, deviceId })
} catch (ex) { } catch (ex) {
@@ -244,17 +253,21 @@ export const createParentRouter = ({ database, websocket }: {database: Database,
throw new BadRequest() throw new BadRequest()
} }
const deviceEntry = await assertAuthValidAndReturnDeviceEntry({ await database.transaction(async (transaction) => {
deviceAuthToken: req.body.deviceAuthToken, const deviceEntry = await assertAuthValidAndReturnDeviceEntry({
parentId: req.body.parentUserId, deviceAuthToken: req.body.deviceAuthToken,
secondPasswordHash: req.body.parentPasswordSecondHash parentId: req.body.parentUserId,
}) secondPasswordHash: req.body.parentPasswordSecondHash,
transaction
})
await removeDevice({ await removeDevice({
database, database,
familyId: deviceEntry.familyId, familyId: deviceEntry.familyId,
deviceId: req.body.deviceId, deviceId: req.body.deviceId,
websocket websocket,
transaction
})
}) })
res.json({ ok: true }) res.json({ ok: true })
+53 -46
View File
@@ -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
@@ -47,12 +47,15 @@ export const createPurchaseRouter = ({ database, websocket }: {
throw new BadRequest() throw new BadRequest()
} }
const familyEntry = await requireFamilyEntry({ const result: boolean = await database.transaction(async (transaction) => {
database, const familyEntry = await requireFamilyEntry({
deviceAuthToken: req.body.deviceAuthToken database,
}) deviceAuthToken: req.body.deviceAuthToken,
transaction
})
const result = canDoNextPurchase({ fullVersionUntil: parseInt(familyEntry.fullVersionUntil, 10) }) return canDoNextPurchase({ fullVersionUntil: parseInt(familyEntry.fullVersionUntil, 10) })
})
res.json({ res.json({
canDoPurchase: result ? 'yes' : 'no due to old purchase', canDoPurchase: result ? 'yes' : 'no due to old purchase',
@@ -69,56 +72,60 @@ export const createPurchaseRouter = ({ database, websocket }: {
throw new BadRequest() throw new BadRequest()
} }
const deviceEntryUnsafe = await database.device.findOne({ await database.transaction(async (transaction) => {
where: { const deviceEntryUnsafe = await database.device.findOne({
deviceAuthToken: req.body.deviceAuthToken where: {
}, deviceAuthToken: req.body.deviceAuthToken
attributes: ['familyId'] },
}) attributes: ['familyId'],
transaction
})
if (!deviceEntryUnsafe) { if (!deviceEntryUnsafe) {
throw new Unauthorized() throw new Unauthorized()
} }
const deviceEntry = { const deviceEntry = {
familyId: deviceEntryUnsafe.familyId familyId: deviceEntryUnsafe.familyId
} }
if (!isGooglePlayPurchaseSignatureValid({ if (!isGooglePlayPurchaseSignatureValid({
receipt: req.body.receipt, receipt: req.body.receipt,
signature: req.body.signature signature: req.body.signature
})) { })) {
throw new Conflict() throw new Conflict()
} }
const receipt = JSON.parse(req.body.receipt) const receipt = JSON.parse(req.body.receipt)
if (typeof receipt !== 'object') { if (typeof receipt !== 'object') {
throw new Conflict() throw new Conflict()
} }
let type: 'month' | 'year' let type: 'month' | 'year'
if (receipt.productId === 'premium_year_2018') { if (receipt.productId === 'premium_year_2018') {
type = 'year' type = 'year'
} else if (receipt.productId === 'premium_month_2018') { } else if (receipt.productId === 'premium_month_2018') {
type = 'month' type = 'month'
} else { } else {
throw new Conflict() throw new Conflict()
} }
const orderId = receipt.orderId const orderId = receipt.orderId
if (typeof orderId !== 'string') { if (typeof orderId !== 'string') {
throw new Conflict() throw new Conflict()
} }
await addPurchase({ await addPurchase({
database, database,
familyId: deviceEntry.familyId, familyId: deviceEntry.familyId,
type, type,
transactionId: orderId, transactionId: orderId,
websocket websocket,
transaction
})
}) })
res.json({ ok: true }) res.json({ ok: true })
+13 -9
View File
@@ -20,13 +20,15 @@ import { optionalPasswordRegex, optionalSaltRegex } from '../util/password'
export interface ClientPushChangesRequest { export interface ClientPushChangesRequest {
deviceAuthToken: string deviceAuthToken: string
actions: Array<{ actions: Array<ClientPushChangesRequestAction>
encodedAction: string }
sequenceNumber: number
integrity: string export interface ClientPushChangesRequestAction {
type: 'appLogic' | 'parent' | 'child' encodedAction: string
userId: string sequenceNumber: number
}> integrity: string
type: 'appLogic' | 'parent' | 'child'
userId: string
} }
export interface ClientPullChangesRequest { export interface ClientPullChangesRequest {
@@ -50,14 +52,16 @@ export interface ParentPassword {
export const assertParentPasswordValid = (password: ParentPassword) => { export const assertParentPasswordValid = (password: ParentPassword) => {
if (password.hash === '' || password.secondHash === '' || password.secondSalt === '') { if (password.hash === '' || password.secondHash === '' || password.secondSalt === '') {
throw new Error('missing fields at parent password') throw new ParentPasswordValidationException('missing fields at parent password')
} }
if (!(optionalPasswordRegex.test(password.hash) && optionalPasswordRegex.test(password.secondHash) && optionalSaltRegex.test(password.secondSalt))) { if (!(optionalPasswordRegex.test(password.hash) && optionalPasswordRegex.test(password.secondHash) && optionalSaltRegex.test(password.secondSalt))) {
throw new Error('invalid parent password') throw new ParentPasswordValidationException('invalid parent password')
} }
} }
export class ParentPasswordValidationException extends Error {}
export interface CreateFamilyByMailTokenRequest { export interface CreateFamilyByMailTokenRequest {
mailAuthToken: string mailAuthToken: string
parentPassword: ParentPassword parentPassword: ParentPassword
+22 -17
View File
@@ -85,7 +85,7 @@ export const createSyncRouter = ({ database, websocket, connectedDevicesManager,
throw new BadRequest() throw new BadRequest()
} }
await database.transaction(async (transaction) => { const serverStatus = await database.transaction(async (transaction) => {
const deviceEntryUnsafe = await database.device.findOne({ const deviceEntryUnsafe = await database.device.findOne({
where: { where: {
deviceAuthToken: body.deviceAuthToken deviceAuthToken: body.deviceAuthToken
@@ -112,23 +112,23 @@ export const createSyncRouter = ({ database, websocket, connectedDevicesManager,
}) })
} }
const serverStatus = await generateServerDataStatus({ return generateServerDataStatus({
database, database,
familyId, familyId,
clientStatus: body.status, clientStatus: body.status,
transaction transaction
}) })
if (serverStatus.devices) { eventHandler.countEvent('pullStatusRequest devices') }
if (serverStatus.apps) { eventHandler.countEvent('pullStatusRequest apps') }
if (serverStatus.categoryBase) { eventHandler.countEvent('pullStatusRequest categoryBase') }
if (serverStatus.categoryApp) { eventHandler.countEvent('pullStatusRequest categoryApp') }
if (serverStatus.usedTimes) { eventHandler.countEvent('pullStatusRequest usedTimes') }
if (serverStatus.rules) { eventHandler.countEvent('pullStatusRequest rules') }
if (serverStatus.users) { eventHandler.countEvent('pullStatusRequest users') }
res.json(serverStatus)
}) })
if (serverStatus.devices) { eventHandler.countEvent('pullStatusRequest devices') }
if (serverStatus.apps) { eventHandler.countEvent('pullStatusRequest apps') }
if (serverStatus.categoryBase) { eventHandler.countEvent('pullStatusRequest categoryBase') }
if (serverStatus.categoryApp) { eventHandler.countEvent('pullStatusRequest categoryApp') }
if (serverStatus.usedTimes) { eventHandler.countEvent('pullStatusRequest usedTimes') }
if (serverStatus.rules) { eventHandler.countEvent('pullStatusRequest rules') }
if (serverStatus.users) { eventHandler.countEvent('pullStatusRequest users') }
res.json(serverStatus)
} catch (ex) { } catch (ex) {
next(ex) next(ex)
} }
@@ -158,14 +158,19 @@ export const createSyncRouter = ({ database, websocket, connectedDevicesManager,
throw new BadRequest() throw new BadRequest()
} }
const removedEntry = await database.oldDevice.findOne({ const isDeviceRemoved: boolean = await database.transaction(async (transaction) => {
where: { const removedEntry = await database.oldDevice.findOne({
deviceAuthToken: req.body.deviceAuthToken where: {
} deviceAuthToken: req.body.deviceAuthToken
},
transaction
})
return !!removedEntry
}) })
res.json({ res.json({
isDeviceRemoved: !!removedEntry isDeviceRemoved
}) })
} catch (ex) { } catch (ex) {
next(ex) next(ex)
+35 -31
View File
@@ -4,6 +4,39 @@ const Ajv = require('ajv')
const ajv = new Ajv() const ajv = new Ajv()
const definitions = { const definitions = {
"ClientPushChangesRequestAction": {
"type": "object",
"properties": {
"encodedAction": {
"type": "string"
},
"sequenceNumber": {
"type": "number"
},
"integrity": {
"type": "string"
},
"type": {
"enum": [
"appLogic",
"child",
"parent"
],
"type": "string"
},
"userId": {
"type": "string"
}
},
"additionalProperties": false,
"required": [
"encodedAction",
"integrity",
"sequenceNumber",
"type",
"userId"
]
},
"ClientDataStatus": { "ClientDataStatus": {
"type": "object", "type": "object",
"properties": { "properties": {
@@ -2219,37 +2252,7 @@ export const isClientPushChangesRequest: (value: object) => value is ClientPushC
"actions": { "actions": {
"type": "array", "type": "array",
"items": { "items": {
"type": "object", "$ref": "#/definitions/ClientPushChangesRequestAction"
"properties": {
"encodedAction": {
"type": "string"
},
"sequenceNumber": {
"type": "number"
},
"integrity": {
"type": "string"
},
"type": {
"enum": [
"appLogic",
"child",
"parent"
],
"type": "string"
},
"userId": {
"type": "string"
}
},
"additionalProperties": false,
"required": [
"encodedAction",
"integrity",
"sequenceNumber",
"type",
"userId"
]
} }
} }
}, },
@@ -2258,6 +2261,7 @@ export const isClientPushChangesRequest: (value: object) => value is ClientPushC
"actions", "actions",
"deviceAuthToken" "deviceAuthToken"
], ],
"definitions": definitions,
"$schema": "http://json-schema.org/draft-07/schema#" "$schema": "http://json-schema.org/draft-07/schema#"
}) })
export const isClientPullChangesRequest: (value: object) => value is ClientPullChangesRequest = ajv.compile({ export const isClientPullChangesRequest: (value: object) => value is ClientPullChangesRequest = ajv.compile({
+3 -1
View File
@@ -29,7 +29,7 @@ function parseYesNo (value: string) {
} else if (value === 'no') { } else if (value === 'no') {
return false return false
} else { } else {
throw new Error('invalid value "' + value + '", expected "yes" or "no"') throw new ParseYesNoException('invalid value "' + value + '", expected "yes" or "no"')
} }
} }
@@ -39,3 +39,5 @@ export const config: Config = {
pingInterval: parseInt(process.env.PING_INTERVAL_SEC || '25', 10) * 1000, pingInterval: parseInt(process.env.PING_INTERVAL_SEC || '25', 10) * 1000,
alwaysPro: process.env.ALWAYS_PRO ? parseYesNo(process.env.ALWAYS_PRO) : false alwaysPro: process.env.ALWAYS_PRO ? parseYesNo(process.env.ALWAYS_PRO) : false
} }
class ParseYesNoException extends Error {}
+2
View File
@@ -34,6 +34,8 @@ export type AppModelStatic = typeof Sequelize.Model & {
new (values?: object, options?: Sequelize.BuildOptions): AppModel; new (values?: object, options?: Sequelize.BuildOptions): AppModel;
} }
export const maxPackageNameLength = 255
export const attributes: SequelizeAttributes<AppAttributes> = { export const attributes: SequelizeAttributes<AppAttributes> = {
familyId: { familyId: {
...familyIdColumn, ...familyIdColumn,
+3
View File
@@ -32,6 +32,9 @@ export type AppActivityModelStatic = typeof Sequelize.Model & {
new (values?: object, options?: Sequelize.BuildOptions): AppActivityModel; new (values?: object, options?: Sequelize.BuildOptions): AppActivityModel;
} }
export const maxPackageNameLength = 255
export const maxActivityNameLength = 255
export const attributes: SequelizeAttributes<AppActivityAttributes> = { export const attributes: SequelizeAttributes<AppActivityAttributes> = {
familyId: { familyId: {
...familyIdColumn, ...familyIdColumn,
+3 -2
View File
@@ -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
@@ -46,5 +46,6 @@ export const attributes: SequelizeAttributes<ConfigAttributes> = {
export const createConfigModel = (sequelize: Sequelize.Sequelize): ConfigModelStatic => sequelize.define('Config', attributes) as ConfigModelStatic export const createConfigModel = (sequelize: Sequelize.Sequelize): ConfigModelStatic => sequelize.define('Config', attributes) as ConfigModelStatic
export const configItemIds = { export const configItemIds = {
statusMessage: 'status_message' statusMessage: 'status_message',
selfTestData: 'self_test_data'
} }
+54 -4
View File
@@ -15,7 +15,9 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { Promise as BluePromise } from 'bluebird'
import * as Sequelize from 'sequelize' import * as Sequelize from 'sequelize'
import { generateIdWithinFamily } from '../util/token'
import { AddDeviceTokenModelStatic, createAddDeviceTokenModel } from './adddevicetoken' import { AddDeviceTokenModelStatic, createAddDeviceTokenModel } from './adddevicetoken'
import { AppModelStatic, createAppModel } from './app' import { AppModelStatic, createAppModel } from './app'
import { AppActivityModelStatic, createAppActivityModel } from './appactivity' import { AppActivityModelStatic, createAppActivityModel } from './appactivity'
@@ -23,7 +25,7 @@ import { AuthTokenModelStatic, createAuthtokenModel } from './authtoken'
import { CategoryModelStatic, createCategoryModel } from './category' import { CategoryModelStatic, createCategoryModel } from './category'
import { CategoryAppModelStatic, createCategoryAppModel } from './categoryapp' import { CategoryAppModelStatic, createCategoryAppModel } from './categoryapp'
import { CategoryNetworkIdModelStatic, createCategoryNetworkIdModel } from './categorynetworkid' import { CategoryNetworkIdModelStatic, createCategoryNetworkIdModel } from './categorynetworkid'
import { ConfigModelStatic, createConfigModel } from './config' import { configItemIds, ConfigModelStatic, createConfigModel } from './config'
import { createDeviceModel, DeviceModelStatic } from './device' import { createDeviceModel, DeviceModelStatic } from './device'
import { createFamilyModel, FamilyModelStatic } from './family' import { createFamilyModel, FamilyModelStatic } from './family'
import { createMailLoginTokenModel, MailLoginTokenModelStatic } from './maillogintoken' import { createMailLoginTokenModel, MailLoginTokenModelStatic } from './maillogintoken'
@@ -36,6 +38,8 @@ import { createUsedTimeModel, UsedTimeModelStatic } from './usedtime'
import { createUserModel, UserModelStatic } from './user' import { createUserModel, UserModelStatic } from './user'
import { createUserLimitLoginCategoryModel, UserLimitLoginCategoryModelStatic } from './userlimitlogincategory' import { createUserLimitLoginCategoryModel, UserLimitLoginCategoryModelStatic } from './userlimitlogincategory'
export type Transaction = Sequelize.Transaction
export interface Database { export interface Database {
addDeviceToken: AddDeviceTokenModelStatic addDeviceToken: AddDeviceTokenModelStatic
authtoken: AuthTokenModelStatic authtoken: AuthTokenModelStatic
@@ -55,7 +59,7 @@ export interface Database {
usedTime: UsedTimeModelStatic usedTime: UsedTimeModelStatic
user: UserModelStatic user: UserModelStatic
userLimitLoginCategory: UserLimitLoginCategoryModelStatic userLimitLoginCategory: UserLimitLoginCategoryModelStatic
transaction: <T> (autoCallback: (t: Sequelize.Transaction) => Promise<T>) => Promise<T> transaction: <T> (autoCallback: (t: Transaction) => Promise<T>, options?: { transaction: Transaction }) => Promise<T>
dialect: string dialect: string
} }
@@ -78,8 +82,9 @@ const createDatabase = (sequelize: Sequelize.Sequelize): Database => ({
usedTime: createUsedTimeModel(sequelize), usedTime: createUsedTimeModel(sequelize),
user: createUserModel(sequelize), user: createUserModel(sequelize),
userLimitLoginCategory: createUserLimitLoginCategoryModel(sequelize), userLimitLoginCategory: createUserLimitLoginCategoryModel(sequelize),
transaction: <T> (autoCallback: (transaction: Sequelize.Transaction) => Promise<T>) => (sequelize.transaction({ transaction: <T> (autoCallback: (transaction: Transaction) => Promise<T>, options?: { transaction: Transaction }) => (sequelize.transaction({
isolationLevel: Sequelize.Transaction.ISOLATION_LEVELS.READ_COMMITTED isolationLevel: Sequelize.Transaction.ISOLATION_LEVELS.READ_COMMITTED,
transaction: options?.transaction
}, autoCallback) as any) as Promise<T>, }, autoCallback) as any) as Promise<T>,
dialect: sequelize.getDialect() dialect: sequelize.getDialect()
}) })
@@ -93,3 +98,48 @@ export const sequelize = new Sequelize.Sequelize(process.env.DATABASE_URL || 'sq
export const defaultDatabase = createDatabase(sequelize) export const defaultDatabase = createDatabase(sequelize)
export const defaultUmzug = createUmzug(sequelize) export const defaultUmzug = createUmzug(sequelize)
class NestedTransactionTestException extends Error {}
class TestRollbackException extends NestedTransactionTestException {}
class NestedTransactionsNotWorkingException extends NestedTransactionTestException { constructor () { super('NestedTransactionsNotWorkingException') } }
class IllegalStateException extends NestedTransactionTestException {}
export const wrapPromise = <T>(promise: Promise<T>) => BluePromise.resolve(promise)
export const warpPromiseReturner = <T>(fun: () => Promise<T>) => () => wrapPromise(fun())
export async function assertNestedTransactionsAreWorking (database: Database) {
const testValue = generateIdWithinFamily()
// clean up just for the case
await database.config.destroy({ where: { id: configItemIds.selfTestData } })
await database.transaction(async (transaction) => {
const readOne = await database.config.findOne({ where: { id: configItemIds.selfTestData }, transaction })
if (readOne) throw new IllegalStateException()
await database.transaction(async (transaction) => {
await database.config.create({ id: configItemIds.selfTestData, value: testValue }, { transaction })
const readTwo = await database.config.findOne({ where: { id: configItemIds.selfTestData }, transaction })
if (readTwo?.value !== testValue) throw new IllegalStateException()
try {
await database.transaction(async (transaction) => {
await database.config.destroy({ where: { id: configItemIds.selfTestData }, transaction })
throw new TestRollbackException()
}, { transaction })
} catch (ex) {
if (!(ex instanceof TestRollbackException)) throw ex
}
const readThree = await database.config.findOne({ where: { id: configItemIds.selfTestData }, transaction })
if (readThree?.value !== testValue) throw new NestedTransactionsNotWorkingException()
await database.config.destroy({ where: { id: configItemIds.selfTestData }, transaction })
}, { transaction })
})
}
+3 -2
View File
@@ -16,6 +16,7 @@
*/ */
import * as Sequelize from 'sequelize' import * as Sequelize from 'sequelize'
import { ValidationException } from '../exception'
import { MinuteOfDay } from '../util/minuteofday' import { MinuteOfDay } from '../util/minuteofday'
import { familyIdColumn, idWithinFamilyColumn, timestampColumn } from './columns' import { familyIdColumn, idWithinFamilyColumn, timestampColumn } from './columns'
import { SequelizeAttributes } from './types' import { SequelizeAttributes } from './types'
@@ -85,11 +86,11 @@ export const attributesVersion1: SequelizeAttributes<SessionDurationAttributesVe
const startMinuteOfDay = this.startMinuteOfDay const startMinuteOfDay = this.startMinuteOfDay
if (typeof endMinuteOfDay !== 'number' || typeof startMinuteOfDay !== 'number') { if (typeof endMinuteOfDay !== 'number' || typeof startMinuteOfDay !== 'number') {
throw new Error('wrong data types') throw new ValidationException({ staticMessage: 'wrong data types for start and end minute at the session duration' })
} }
if (startMinuteOfDay > endMinuteOfDay) { if (startMinuteOfDay > endMinuteOfDay) {
throw new Error('startMinuteOfDay must not be bigger than endMinuteOfDay') throw new ValidationException({ staticMessage: 'startMinuteOfDay must not be bigger than endMinuteOfDay for a session duration' })
} }
} }
} }
+3 -2
View File
@@ -16,6 +16,7 @@
*/ */
import * as Sequelize from 'sequelize' import * as Sequelize from 'sequelize'
import { ValidationException } from '../exception'
import { MinuteOfDay } from '../util/minuteofday' import { MinuteOfDay } from '../util/minuteofday'
import { booleanColumn, familyIdColumn, idWithinFamilyColumn } from './columns' import { booleanColumn, familyIdColumn, idWithinFamilyColumn } from './columns'
import { SequelizeAttributes } from './types' import { SequelizeAttributes } from './types'
@@ -90,11 +91,11 @@ export const attributesVersion2: SequelizeAttributes<TimelimitRuleAttributesVers
const startMinuteOfDay = this.startMinuteOfDay const startMinuteOfDay = this.startMinuteOfDay
if (typeof endMinuteOfDay !== 'number' || typeof startMinuteOfDay !== 'number') { if (typeof endMinuteOfDay !== 'number' || typeof startMinuteOfDay !== 'number') {
throw new Error('wrong data types') throw new ValidationException({ staticMessage: 'wrong data types for start and end minute at the time limit rule' })
} }
if (startMinuteOfDay > endMinuteOfDay) { if (startMinuteOfDay > endMinuteOfDay) {
throw new Error('startMinuteOfDay must not be bigger than endMinuteOfDay') throw new ValidationException({ staticMessage: 'startMinuteOfDay must not be bigger than endMinuteOfDay for a time limit rule' })
} }
} }
}, },
+3 -2
View File
@@ -16,6 +16,7 @@
*/ */
import * as Sequelize from 'sequelize' import * as Sequelize from 'sequelize'
import { ValidationException } from '../exception'
import { MinuteOfDay } from '../util/minuteofday' import { MinuteOfDay } from '../util/minuteofday'
import { familyIdColumn, idWithinFamilyColumn, timestampColumn } from './columns' import { familyIdColumn, idWithinFamilyColumn, timestampColumn } from './columns'
import { SequelizeAttributes } from './types' import { SequelizeAttributes } from './types'
@@ -100,11 +101,11 @@ export const attributesVersion3: SequelizeAttributes<UsedTimeAttributesVersion3>
const startMinuteOfDay = this.startMinuteOfDay const startMinuteOfDay = this.startMinuteOfDay
if (typeof endMinuteOfDay !== 'number' || typeof startMinuteOfDay !== 'number') { if (typeof endMinuteOfDay !== 'number' || typeof startMinuteOfDay !== 'number') {
throw new Error('wrong data types') throw new ValidationException({ staticMessage: 'wrong data types for start and end minute at the used time' })
} }
if (startMinuteOfDay > endMinuteOfDay) { if (startMinuteOfDay > endMinuteOfDay) {
throw new Error('startMinuteOfDay must not be bigger than endMinuteOfDay') throw new ValidationException({ staticMessage: 'startMinuteOfDay must not be bigger than endMinuteOfDay for a used time' })
} }
} }
} }
+19
View File
@@ -0,0 +1,19 @@
/*
* 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 { StaticMessageException, IllegalStateException } from './static-message-exception'
export { ValidationException } from './validation'
+30
View File
@@ -0,0 +1,30 @@
/*
* 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 class StaticMessageException extends Error {
readonly staticMessage: string
readonly dynamicMessage?: string
constructor ({ staticMessage, dynamicMessage }: { staticMessage: string, dynamicMessage?: string }) {
super(dynamicMessage || staticMessage)
this.staticMessage = staticMessage
this.dynamicMessage = dynamicMessage
}
}
export class IllegalStateException extends StaticMessageException {}
+20
View File
@@ -0,0 +1,20 @@
/*
* 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 { StaticMessageException } from './static-message-exception'
export class ValidationException extends StaticMessageException {}
+9 -8
View File
@@ -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
@@ -16,26 +16,27 @@
*/ */
import { Unauthorized } from 'http-errors' import { Unauthorized } from 'http-errors'
import { Database } from '../../database' import { Database, Transaction } from '../../database'
import { generateAuthToken } from '../../util/token' import { generateAuthToken } from '../../util/token'
export const createAuthTokenByMailAddress = async ({ mail, database }: {mail: string, database: Database}) => { export const createAuthTokenByMailAddress = async ({ mail, database, transaction }: { mail: string, database: Database, transaction: Transaction }) => {
const token = generateAuthToken() const token = generateAuthToken()
await database.authtoken.create({ await database.authtoken.create({
token, token,
mail, mail,
createdAt: Date.now().toString() createdAt: Date.now().toString()
}) }, { transaction })
return token return token
} }
export const getMailByAuthToken = async ({ mailAuthToken, database }: {mailAuthToken: string, database: Database}) => { export const getMailByAuthToken = async ({ mailAuthToken, database, transaction }: { mailAuthToken: string, database: Database, transaction: Transaction }) => {
const entry = await database.authtoken.findOne({ const entry = await database.authtoken.findOne({
where: { where: {
token: mailAuthToken token: mailAuthToken
} },
transaction
}) })
if (entry) { if (entry) {
@@ -45,8 +46,8 @@ export const getMailByAuthToken = async ({ mailAuthToken, database }: {mailAuthT
} }
} }
export const requireMailByAuthToken = async ({ mailAuthToken, database }: {mailAuthToken: string, database: Database}) => { export const requireMailByAuthToken = async ({ mailAuthToken, database, transaction }: { mailAuthToken: string, database: Database, transaction: Transaction }) => {
const mail = await getMailByAuthToken({ mailAuthToken, database }) const mail = await getMailByAuthToken({ mailAuthToken, database, transaction })
if (!mail) { if (!mail) {
throw new Unauthorized() throw new Unauthorized()
+21 -41
View File
@@ -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
@@ -15,7 +15,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { Forbidden, Gone, InternalServerError, TooManyRequests } from 'http-errors' import { Forbidden, Gone, TooManyRequests } from 'http-errors'
import { Database } from '../../database' import { Database } from '../../database'
import { sendAuthenticationMail } from '../../util/mail' import { sendAuthenticationMail } from '../../util/mail'
import { areWordSequencesEqual, randomWords } from '../../util/random-words' import { areWordSequencesEqual, randomWords } from '../../util/random-words'
@@ -27,7 +27,8 @@ export const sendLoginCode = async ({ mail, locale, database }: {
mail: string mail: string
locale: string locale: string
database: Database database: Database
}): Promise<{mailLoginToken: string}> => { // no transaction here because this is directly called from an API endpoint
}): Promise<{ mailLoginToken: string }> => {
try { try {
await checkMailSendLimit(mail) await checkMailSendLimit(mail)
} catch (ex) { } catch (ex) {
@@ -43,12 +44,14 @@ export const sendLoginCode = async ({ mail, locale, database }: {
locale locale
}) })
await database.mailLoginToken.create({ await database.transaction(async (transaction) => {
mailLoginToken, await database.mailLoginToken.create({
receivedCode: code, mailLoginToken,
mail, receivedCode: code,
createdAt: Date.now().toString(10), mail,
remainingAttempts: 3 createdAt: Date.now().toString(10),
remainingAttempts: 3
}, { transaction })
}) })
return { return {
@@ -62,8 +65,9 @@ export const signInByMailCode = async ({ mailLoginToken, receivedCode, database
mailLoginToken: string mailLoginToken: string
receivedCode: string receivedCode: string
database: Database database: Database
}): Promise<{mailAuthToken: string}> => { // no transaction here because this is directly called from an API endpoint
const { mail, status } = await database.transaction(async (transaction) => { }): Promise<{ mailAuthToken: string }> => {
return database.transaction(async (transaction) => {
const entry = await database.mailLoginToken.findOne({ const entry = await database.mailLoginToken.findOne({
where: { where: {
mailLoginToken mailLoginToken
@@ -72,10 +76,7 @@ export const signInByMailCode = async ({ mailLoginToken, receivedCode, database
}) })
if ((!entry) || entry.remainingAttempts === 0) { if ((!entry) || entry.remainingAttempts === 0) {
return { throw new Gone()
mail: null,
status: 'gone'
}
} }
if (!areWordSequencesEqual(entry.receivedCode, receivedCode)) { if (!areWordSequencesEqual(entry.receivedCode, receivedCode)) {
@@ -84,35 +85,14 @@ export const signInByMailCode = async ({ mailLoginToken, receivedCode, database
await entry.save({ transaction }) await entry.save({ transaction })
if (entry.remainingAttempts === 0) { if (entry.remainingAttempts === 0) {
return { throw new Gone()
mail: null,
status: 'gone'
}
} else { } else {
return { throw new Forbidden()
mail: null,
status: 'forbidden'
}
} }
} }
return { const mailAuthToken = await createAuthTokenByMailAddress({ mail: entry.mail, database, transaction })
mail: entry.mail,
status: null return { mailAuthToken }
}
}) })
if (!mail) {
if (status === 'gone') {
throw new Gone()
} else if (status === 'forbidden') {
throw new Forbidden()
} else {
throw new InternalServerError()
}
}
const mailAuthToken = await createAuthTokenByMailAddress({ mail, database })
return { mailAuthToken }
} }
+8 -12
View File
@@ -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
@@ -21,14 +21,15 @@ import { Database } from '../../database'
import { generateAuthToken, generateVersionId } from '../../util/token' import { generateAuthToken, generateVersionId } from '../../util/token'
import { WebsocketApi } from '../../websocket' import { WebsocketApi } from '../../websocket'
import { prepareDeviceEntry } from '../device/prepare-device-entry' import { prepareDeviceEntry } from '../device/prepare-device-entry'
import { notifyClientsAboutChanges } from '../websocket' import { notifyClientsAboutChangesDelayed } from '../websocket'
export const addChildDevice = async ({ database, websocket, request }: { export const addChildDevice = async ({ database, websocket, request }: {
database: Database database: Database
websocket: WebsocketApi websocket: WebsocketApi
request: RegisterChildDeviceRequest request: RegisterChildDeviceRequest
// no transaction here because this is directly called from an API endpoint
}) => { }) => {
const { response, familyId } = await database.transaction(async (transaction) => { return database.transaction(async (transaction) => {
const entry = await database.addDeviceToken.findOne({ const entry = await database.addDeviceToken.findOne({
where: { where: {
token: request.registerToken.toLowerCase() token: request.registerToken.toLowerCase()
@@ -63,16 +64,11 @@ export const addChildDevice = async ({ database, websocket, request }: {
transaction transaction
}) })
await notifyClientsAboutChangesDelayed({ familyId, websocket, database, isImportant: true, sourceDeviceId: deviceId, transaction })
return { return {
response: { deviceId,
deviceId, deviceAuthToken
deviceAuthToken
},
familyId
} }
}) })
await notifyClientsAboutChanges({ familyId, websocket, database, isImportant: true, sourceDeviceId: response.deviceId })
return response
} }
@@ -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
@@ -23,6 +23,7 @@ export const logoutAtPrimaryDevice = async ({ deviceAuthToken, database, websock
deviceAuthToken: string deviceAuthToken: string
database: Database database: Database
websocket: WebsocketApi websocket: WebsocketApi
// no transaction here because this is directly called from an API endpoint
}) => { }) => {
await database.transaction(async (transaction) => { await database.transaction(async (transaction) => {
const ownDeviceEntryUnsafe = await database.device.findOne({ const ownDeviceEntryUnsafe = await database.device.findOne({
+14 -32
View File
@@ -15,13 +15,13 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { Conflict, Unauthorized } from 'http-errors' import { Conflict, InternalServerError, Unauthorized } from 'http-errors'
import * as Sequelize from 'sequelize' import * as Sequelize from 'sequelize'
import { config } from '../../config' import { config } from '../../config'
import { Database } from '../../database' import { Database } from '../../database'
import { generateVersionId } from '../../util/token' import { generateVersionId } from '../../util/token'
import { WebsocketApi } from '../../websocket' import { WebsocketApi } from '../../websocket'
import { notifyClientsAboutChanges } from '../websocket' import { notifyClientsAboutChangesDelayed } from '../websocket'
export const setPrimaryDevice = async ({ database, websocket, deviceAuthToken, currentUserId, action }: { export const setPrimaryDevice = async ({ database, websocket, deviceAuthToken, currentUserId, action }: {
database: Database database: Database
@@ -29,12 +29,9 @@ export const setPrimaryDevice = async ({ database, websocket, deviceAuthToken, c
deviceAuthToken: string deviceAuthToken: string
currentUserId: string currentUserId: string
action: 'set this device' | 'unset this device' action: 'set this device' | 'unset this device'
// no transaction here because this is directly called from an API endpoint
}): Promise<'assigned to other device' | 'requires full version' | 'success'> => { }): Promise<'assigned to other device' | 'requires full version' | 'success'> => {
const response = await database.transaction(async (transaction): Promise<{ return database.transaction(async (transaction): Promise<'assigned to other device' | 'requires full version' | 'success'> => {
response: 'assigned to other device' | 'requires full version' | 'success',
sourceDeviceId: string,
familyId: string
}> => {
const deviceEntryUnsafe = await database.device.findOne({ const deviceEntryUnsafe = await database.device.findOne({
where: { where: {
deviceAuthToken deviceAuthToken
@@ -106,22 +103,14 @@ export const setPrimaryDevice = async ({ database, websocket, deviceAuthToken, c
} }
if (!(familyEntry.hasFullVersion || config.alwaysPro)) { if (!(familyEntry.hasFullVersion || config.alwaysPro)) {
return { return 'requires full version'
response: 'requires full version',
sourceDeviceId: deviceEntry.deviceId,
familyId: deviceEntry.familyId
}
} }
} }
if (action === 'set this device') { if (action === 'set this device') {
// check that no other device is selected // check that no other device is selected
if (userDeviceEntries.find((item) => item.deviceId === userEntry.currentDevice)) { if (userDeviceEntries.find((item) => item.deviceId === userEntry.currentDevice)) {
return { return 'assigned to other device'
response: 'assigned to other device',
sourceDeviceId: deviceEntry.deviceId,
familyId: deviceEntry.familyId
}
} }
// update // update
@@ -158,7 +147,7 @@ export const setPrimaryDevice = async ({ database, websocket, deviceAuthToken, c
throw new Conflict() throw new Conflict()
} }
} else { } else {
throw new Error('illegal state') throw new InternalServerError('illegal state')
} }
// invalidiate user list // invalidiate user list
@@ -171,23 +160,16 @@ export const setPrimaryDevice = async ({ database, websocket, deviceAuthToken, c
} }
}) })
return {
response: 'success',
sourceDeviceId: deviceEntry.deviceId,
familyId: deviceEntry.familyId
}
})
if (response.response === 'success') {
// trigger sync // trigger sync
await notifyClientsAboutChanges({ await notifyClientsAboutChangesDelayed({
familyId: response.familyId, familyId: deviceEntry.familyId,
sourceDeviceId: response.sourceDeviceId, sourceDeviceId: deviceEntry.deviceId,
websocket, websocket,
database, database,
isImportant: false // the source device knows it already isImportant: false, // the source device knows it already
transaction
}) })
}
return response.response return 'success'
})
} }
+2 -1
View File
@@ -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
@@ -22,6 +22,7 @@ import { Database } from '../../database'
export async function deleteFamilies ({ database, familiyIds }: { export async function deleteFamilies ({ database, familiyIds }: {
database: Database database: Database
familiyIds: Array<string> familiyIds: Array<string>
// no transaction here because this should run isolated
}) { }) {
if (familiyIds.length === 0) { if (familiyIds.length === 0) {
return return
+29 -25
View File
@@ -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
@@ -34,30 +34,34 @@ export async function deleteOldFamilies (database: Database) {
} }
export async function findOldFamilyIds (database: Database) { export async function findOldFamilyIds (database: Database) {
const familyIdsWithExpiredLicenses = await database.family.findAll({ return database.transaction(async (transaction) => {
where: { const familyIdsWithExpiredLicenses = await database.family.findAll({
fullVersionUntil: { where: {
[Sequelize.Op.lt]: (Date.now() - 1000 * 60 * 60 * 24 * 90 /* 90 days */).toString(10) fullVersionUntil: {
} [Sequelize.Op.lt]: (Date.now() - 1000 * 60 * 60 * 24 * 90 /* 90 days */).toString(10)
}, }
attributes: ['familyId']
}).map((item) => item.familyId)
if (familyIdsWithExpiredLicenses.length === 0) {
return []
}
const recentlyUsedFamilyIds = await database.device.findAll({
where: {
familyId: {
[Sequelize.Op.in]: familyIdsWithExpiredLicenses
}, },
lastConnectivity: { attributes: ['familyId'],
[Sequelize.Op.gt]: (Date.now() - 1000 * 60 * 60 * 24 * 90 /* 90 days */).toString(10) transaction
} }).map((item) => item.familyId)
},
attributes: ['familyId']
}).map((item) => item.familyId)
return difference(familyIdsWithExpiredLicenses, recentlyUsedFamilyIds) if (familyIdsWithExpiredLicenses.length === 0) {
return []
}
const recentlyUsedFamilyIds = await database.device.findAll({
where: {
familyId: {
[Sequelize.Op.in]: familyIdsWithExpiredLicenses
},
lastConnectivity: {
[Sequelize.Op.gt]: (Date.now() - 1000 * 60 * 60 * 24 * 90 /* 90 days */).toString(10)
}
},
attributes: ['familyId'],
transaction
}).map((item) => item.familyId)
return difference(familyIdsWithExpiredLicenses, recentlyUsedFamilyIds)
})
} }
+82 -82
View File
@@ -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
@@ -16,102 +16,102 @@
*/ */
import { Conflict } from 'http-errors' import { Conflict } from 'http-errors'
import { Database } from '../../database' import { Database, Transaction } from '../../database'
import { generateVersionId } from '../../util/token' import { generateVersionId } from '../../util/token'
import { WebsocketApi } from '../../websocket' import { WebsocketApi } from '../../websocket'
import { notifyClientsAboutChanges } from '../websocket' import { notifyClientsAboutChangesDelayed } from '../websocket'
export async function removeDevice ({ database, familyId, deviceId, websocket }: { export async function removeDevice ({ database, familyId, deviceId, websocket, transaction }: {
database: Database database: Database
familyId: string familyId: string
deviceId: string deviceId: string
websocket: WebsocketApi websocket: WebsocketApi
transaction: Transaction
}) { }) {
const { oldDeviceAuthToken } = await database.transaction(async (transaction) => { const deviceEntry = await database.device.findOne({
const deviceEntry = await database.device.findOne({ where: {
where: { familyId,
familyId, deviceId
deviceId },
}, transaction
transaction
})
if (!deviceEntry) {
throw new Conflict()
}
// remove app entries
await database.app.destroy({
where: {
familyId,
deviceId
},
transaction
})
await database.appActivity.destroy({
where: {
familyId,
deviceId
},
transaction
})
// remove as current device
await database.user.update({
currentDevice: ''
}, {
where: {
familyId,
currentDevice: deviceId
},
transaction
})
// add to old devices if it is not yet there (it could be there if it reported a uninstall)
const oldOldDeviceEntry = await database.oldDevice.findOne({
where: {
deviceAuthToken: deviceEntry.deviceAuthToken
},
transaction
})
if (!oldOldDeviceEntry) {
await database.oldDevice.create({
deviceAuthToken: deviceEntry.deviceAuthToken
}, {
transaction
})
}
// remove from the device list
await deviceEntry.destroy({ transaction })
// invalidiate the caches
await database.family.update({
deviceListVersion: generateVersionId(),
// the device could have become unassigned during this
userListVersion: generateVersionId()
}, {
where: {
familyId: deviceEntry.familyId
},
transaction
})
return { oldDeviceAuthToken: deviceEntry.deviceAuthToken }
}) })
await notifyClientsAboutChanges({ if (!deviceEntry) {
throw new Conflict()
}
// remove app entries
await database.app.destroy({
where: {
familyId,
deviceId
},
transaction
})
await database.appActivity.destroy({
where: {
familyId,
deviceId
},
transaction
})
// remove as current device
await database.user.update({
currentDevice: ''
}, {
where: {
familyId,
currentDevice: deviceId
},
transaction
})
// add to old devices if it is not yet there (it could be there if it reported a uninstall)
const oldOldDeviceEntry = await database.oldDevice.findOne({
where: {
deviceAuthToken: deviceEntry.deviceAuthToken
},
transaction
})
if (!oldOldDeviceEntry) {
await database.oldDevice.create({
deviceAuthToken: deviceEntry.deviceAuthToken
}, {
transaction
})
}
// remove from the device list
await deviceEntry.destroy({ transaction })
// invalidiate the caches
await database.family.update({
deviceListVersion: generateVersionId(),
// the device could have become unassigned during this
userListVersion: generateVersionId()
}, {
where: {
familyId: deviceEntry.familyId
},
transaction
})
await notifyClientsAboutChangesDelayed({
database, database,
websocket, websocket,
familyId, familyId,
sourceDeviceId: null, sourceDeviceId: null,
isImportant: false isImportant: false,
transaction
}) })
websocket.triggerSyncByDeviceAuthToken({ transaction.afterCommit(() => {
deviceAuthToken: oldDeviceAuthToken, websocket.triggerSyncByDeviceAuthToken({
isImportant: true deviceAuthToken: deviceEntry.deviceAuthToken,
isImportant: true
})
}) })
} }
+21 -23
View File
@@ -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
@@ -15,18 +15,20 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { Unauthorized } from 'http-errors'
import { Database } from '../../database' import { Database } from '../../database'
import { generateAuthToken, generateVersionId } from '../../util/token' import { generateAuthToken, generateVersionId } from '../../util/token'
import { WebsocketApi } from '../../websocket' import { WebsocketApi } from '../../websocket'
import { sendUninstallWarnings } from '../warningmail/uninstall' import { sendUninstallWarnings } from '../warningmail/uninstall'
import { notifyClientsAboutChanges } from '../websocket' import { notifyClientsAboutChangesDelayed } from '../websocket'
export async function reportDeviceRemoved ({ database, deviceAuthToken, websocket }: { export async function reportDeviceRemoved ({ database, deviceAuthToken, websocket }: {
database: Database database: Database
deviceAuthToken: string deviceAuthToken: string
websocket: WebsocketApi websocket: WebsocketApi
// no transaction here because this is directly called from an API endpoint
}) { }) {
const result = await database.transaction(async (transaction) => { await database.transaction(async (transaction) => {
const deviceEntry = await database.device.findOne({ const deviceEntry = await database.device.findOne({
where: { where: {
deviceAuthToken deviceAuthToken
@@ -58,7 +60,21 @@ export async function reportDeviceRemoved ({ database, deviceAuthToken, websocke
transaction transaction
}) })
return { familyId: deviceEntry.familyId, deviceName: deviceEntry.name } await notifyClientsAboutChangesDelayed({
database,
websocket,
familyId: deviceEntry.familyId,
sourceDeviceId: null,
isImportant: false,
transaction
})
await sendUninstallWarnings({
database,
familyId: deviceEntry.familyId,
deviceName: deviceEntry.name,
transaction
})
} else { } else {
const oldDeviceEntry = await database.oldDevice.findOne({ const oldDeviceEntry = await database.oldDevice.findOne({
where: { where: {
@@ -68,26 +84,8 @@ export async function reportDeviceRemoved ({ database, deviceAuthToken, websocke
}) })
if (!oldDeviceEntry) { if (!oldDeviceEntry) {
throw new Error('device not found') throw new Unauthorized('device not found')
} }
return null
} }
}) })
if (result) {
await notifyClientsAboutChanges({
database,
websocket,
familyId: result.familyId,
sourceDeviceId: null,
isImportant: false
})
await sendUninstallWarnings({
database,
familyId: result.familyId,
deviceName: result.deviceName
})
}
} }
+15 -11
View File
@@ -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
@@ -22,16 +22,20 @@ export const canRecoverPassword = async ({ database, mailAuthToken, parentUserId
database: Database database: Database
mailAuthToken: string mailAuthToken: string
parentUserId: string parentUserId: string
}) => { // no transaction here because this is directly called from an API endpoint
const mail = await requireMailByAuthToken({ mailAuthToken, database }) }): Promise<boolean> => {
return database.transaction(async (transaction) => {
const mail = await requireMailByAuthToken({ mailAuthToken, database, transaction })
const entry = await database.user.findOne({ const entry = await database.user.findOne({
where: { where: {
mail, mail,
userId: parentUserId, userId: parentUserId,
type: 'parent' type: 'parent'
} },
transaction
})
return !!entry
}) })
return !!entry
} }
@@ -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
@@ -15,13 +15,14 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { Database } from '../../database' import { Database, Transaction } from '../../database'
import { randomWords } from '../../util/random-words' import { randomWords } from '../../util/random-words'
import { generateIdWithinFamily } from '../../util/token' import { generateIdWithinFamily } from '../../util/token'
export const createAddDeviceToken = async ({ familyId, database }: { export const createAddDeviceToken = async ({ familyId, database, transaction }: {
familyId: string familyId: string
database: Database database: Database
transaction: Transaction
}) => { }) => {
const token = randomWords(5) const token = randomWords(5)
const deviceId = generateIdWithinFamily() const deviceId = generateIdWithinFamily()
@@ -29,7 +30,8 @@ export const createAddDeviceToken = async ({ familyId, database }: {
await database.addDeviceToken.destroy({ await database.addDeviceToken.destroy({
where: { where: {
familyId familyId
} },
transaction
}) })
await database.addDeviceToken.create({ await database.addDeviceToken.create({
@@ -37,7 +39,7 @@ export const createAddDeviceToken = async ({ familyId, database }: {
token: token.toLowerCase(), token: token.toLowerCase(),
deviceId, deviceId,
createdAt: Date.now().toString() createdAt: Date.now().toString()
}) }, { transaction })
return { token, deviceId } return { token, deviceId }
} }

Some files were not shown because too many files have changed in this diff Show More