mirror of
https://codeberg.org/timelimit/timelimit-server.git
synced 2026-08-31 19:03:45 +02:00
Compare commits
62
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
63c23060f6 | ||
|
|
ac9f655c44 | ||
|
|
c3793f71a8 | ||
|
|
717d8f243a | ||
|
|
c693cfdbd4 | ||
|
|
6189601459 | ||
|
|
d3675d484b | ||
|
|
569e5ce62d | ||
|
|
053c59899b | ||
|
|
6220cc6bb9 | ||
|
|
c1e2ea8c84 | ||
|
|
33d9fd732f | ||
|
|
07ee3c268a | ||
|
|
764f240707 | ||
|
|
0f92e6b1b7 | ||
|
|
b392ca295a | ||
|
|
b4a3862c1e | ||
|
|
f5fc8e6cd6 | ||
|
|
24fa354f09 | ||
|
|
9c2048af64 | ||
|
|
3d307bee6e | ||
|
|
b69271f7df | ||
|
|
d227f112e4 | ||
|
|
97d2730b20 | ||
|
|
d83b8a2c26 | ||
|
|
0346197c23 | ||
|
|
333ac4a8a2 | ||
|
|
a7ed01af74 | ||
|
|
77e39752ed | ||
|
|
f77d91ff56 | ||
|
|
7c81f50ad2 | ||
|
|
2d035da0da | ||
|
|
44e7d16b5c | ||
|
|
2d73cba90e | ||
|
|
473fbe80e9 | ||
|
|
1930dd0a27 | ||
|
|
1918c74277 | ||
|
|
a79fcf235e | ||
|
|
cb1347fffa | ||
|
|
48ecbd8ada | ||
|
|
e55d1fd1a9 | ||
|
|
e6cc08a292 | ||
|
|
f10b79a023 | ||
|
|
120ea33547 | ||
|
|
2c401288a3 | ||
|
|
89f3325a18 | ||
|
|
7aaad00881 | ||
|
|
c7e4cfc9f9 | ||
|
|
12ed5d73cd | ||
|
|
4df809a306 | ||
|
|
8ec0781859 | ||
|
|
376a2cc624 | ||
|
|
e14237be7d | ||
|
|
4183ea615a | ||
|
|
e46f5bea3f | ||
|
|
05fac79849 | ||
|
|
1e5da1b95e | ||
|
|
dc5e2baebd | ||
|
|
73465ebe6e | ||
|
|
98ee0fe94e | ||
|
|
d9a032823e | ||
|
|
ae044c19d6 |
@@ -41,6 +41,10 @@ If a whitelist was configured and the mail address is not within it: ``{"mailAdd
|
|||||||
|
|
||||||
If a blacklist was configured and the mail server is within it: ``{"mailServerBlacklisted": true}``
|
If a blacklist was configured and the mail server is within it: ``{"mailServerBlacklisted": true}``
|
||||||
|
|
||||||
|
If a temporarily blacklist was configured and the mail server is within it (used in case of known mail delivery issues): ``{"mailServerBlacklistedTemporarily": true}``
|
||||||
|
|
||||||
|
If the client is suspicious: ``{"blockedForIntegrityReasons": true}``
|
||||||
|
|
||||||
On success: a object with a ``mailLoginToken`` of the type string
|
On success: a object with a ``mailLoginToken`` of the type string
|
||||||
|
|
||||||
#### example response
|
#### example response
|
||||||
|
|||||||
+1
-2
@@ -16,8 +16,7 @@ On a invalid request body: HTTP status code 400 Bad request
|
|||||||
|
|
||||||
On a invalid add device token: HTTP status code 401 Unauthorized
|
On a invalid add device token: HTTP status code 401 Unauthorized
|
||||||
|
|
||||||
On success: a JSON object with the properties ``deviceAuthToken`` and ``ownDeviceId``,
|
On success: object with ``deviceAuthToken`` (string), ``ownDeviceId`` (string) and ``data`` (like a ``/sync/pull-status`` response)
|
||||||
both of the type string
|
|
||||||
|
|
||||||
## POST /child/update-primary-device
|
## POST /child/update-primary-device
|
||||||
|
|
||||||
|
|||||||
+26
-2
@@ -44,7 +44,7 @@ If the mail auth token is invalid/ expired: HTTP status code 401 Unauthorized
|
|||||||
|
|
||||||
If there is already a user with the mail address of the mail auth token: HTTP status code 409 Conflict
|
If there is already a user with the mail address of the mail auth token: HTTP status code 409 Conflict
|
||||||
|
|
||||||
On success: object with ``deviceAuthToken`` (string) and ``ownDeviceId`` (string)
|
On success: object with ``deviceAuthToken`` (string), ``ownDeviceId`` (string) and ``data`` (like a ``/sync/pull-status`` response)
|
||||||
|
|
||||||
## POST /parent/sign-in-into-family
|
## POST /parent/sign-in-into-family
|
||||||
|
|
||||||
@@ -60,7 +60,7 @@ On a invalid request body: HTTP status code 400 Bad Request
|
|||||||
|
|
||||||
If there is no user with the mail address of the mail auth token: HTTP status code 409 Conflict
|
If there is no user with the mail address of the mail auth token: HTTP status code 409 Conflict
|
||||||
|
|
||||||
On success: object with ``deviceAuthToken`` (string) and ``ownDeviceId`` (string)
|
On success: object with ``deviceAuthToken`` (string), ``ownDeviceId`` (string) and ``data`` (like a ``/sync/pull-status`` response)
|
||||||
|
|
||||||
## POST /parent/can-recover-password
|
## POST /parent/can-recover-password
|
||||||
|
|
||||||
@@ -194,3 +194,27 @@ If the ``secondPasswordHash`` is invalid: HTTP status code 401 Unauthorized
|
|||||||
If the server does not support this request: HTTP status code 404
|
If the server does not support this request: HTTP status code 404
|
||||||
|
|
||||||
On success: ``{"token": "some string"}``; you should not make any assumptions about the token string
|
On success: ``{"token": "some string"}``; you should not make any assumptions about the token string
|
||||||
|
|
||||||
|
## POST /parent/delete-account
|
||||||
|
|
||||||
|
Use this to delete an account. This includes the complete family registration
|
||||||
|
with users and devices. Due to that, all parents with a linked mail address
|
||||||
|
have to authenticate this action.
|
||||||
|
|
||||||
|
## request
|
||||||
|
|
||||||
|
see [this JSON schema](../schema/deleteaccountpayload.md)
|
||||||
|
|
||||||
|
## response
|
||||||
|
|
||||||
|
On success: HTTP status code 200
|
||||||
|
|
||||||
|
On a invalid request body: HTTP status code 400 Bad Request
|
||||||
|
|
||||||
|
On unknown device auth token: HTTP status code 401 Unauthorized
|
||||||
|
|
||||||
|
On missing parent authentication: HTTP status code 401 Unauthorized
|
||||||
|
|
||||||
|
On unrelated parent authentication: HTTP status code 401 Unauthorized
|
||||||
|
|
||||||
|
If a newer endpoint must be used/the client is too old: HTTP status code 410 Gone
|
||||||
|
|||||||
@@ -18,6 +18,9 @@
|
|||||||
},
|
},
|
||||||
"parentName": {
|
"parentName": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
|
},
|
||||||
|
"clientLevel": {
|
||||||
|
"type": "number"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"additionalProperties": false,
|
"additionalProperties": false,
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"deviceAuthToken": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"mailAuthTokens": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"additionalProperties": false,
|
||||||
|
"required": [
|
||||||
|
"deviceAuthToken",
|
||||||
|
"mailAuthTokens"
|
||||||
|
],
|
||||||
|
"definitions": {},
|
||||||
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||||
|
"title": "DeleteAccountPayload",
|
||||||
|
"$id": "https://timelimit.io/DeleteAccountPayload"
|
||||||
|
}
|
||||||
@@ -14,6 +14,8 @@
|
|||||||
|
|
||||||
* [CreateRegisterDeviceTokenRequest](./createregisterdevicetokenrequest.md) – `https://timelimit.io/CreateRegisterDeviceTokenRequest`
|
* [CreateRegisterDeviceTokenRequest](./createregisterdevicetokenrequest.md) – `https://timelimit.io/CreateRegisterDeviceTokenRequest`
|
||||||
|
|
||||||
|
* [DeleteAccountPayload](./deleteaccountpayload.md) – `https://timelimit.io/DeleteAccountPayload`
|
||||||
|
|
||||||
* [FinishPurchaseByGooglePlayRequest](./finishpurchasebygoogleplayrequest.md) – `https://timelimit.io/FinishPurchaseByGooglePlayRequest`
|
* [FinishPurchaseByGooglePlayRequest](./finishpurchasebygoogleplayrequest.md) – `https://timelimit.io/FinishPurchaseByGooglePlayRequest`
|
||||||
|
|
||||||
* [IdentityTokenPayload](./identitytokenpayload.md) – `https://timelimit.io/IdentityTokenPayload`
|
* [IdentityTokenPayload](./identitytokenpayload.md) – `https://timelimit.io/IdentityTokenPayload`
|
||||||
@@ -282,6 +284,8 @@
|
|||||||
|
|
||||||
* [Untitled array in ClientPushChangesRequest](./clientpushchangesrequest-properties-actions.md) – `https://timelimit.io/ClientPushChangesRequest#/properties/actions`
|
* [Untitled array in ClientPushChangesRequest](./clientpushchangesrequest-properties-actions.md) – `https://timelimit.io/ClientPushChangesRequest#/properties/actions`
|
||||||
|
|
||||||
|
* [Untitled array in DeleteAccountPayload](./deleteaccountpayload-properties-mailauthtokens.md) – `https://timelimit.io/DeleteAccountPayload#/properties/mailAuthTokens`
|
||||||
|
|
||||||
* [Untitled array in SerializedAppLogicAction](./serializedapplogicaction-definitions-serializedaddinstalledappsaction-properties-apps.md) – `https://timelimit.io/SerializedAppLogicAction#/definitions/SerializedAddInstalledAppsAction/properties/apps`
|
* [Untitled array in SerializedAppLogicAction](./serializedapplogicaction-definitions-serializedaddinstalledappsaction-properties-apps.md) – `https://timelimit.io/SerializedAppLogicAction#/definitions/SerializedAddInstalledAppsAction/properties/apps`
|
||||||
|
|
||||||
* [Untitled array in SerializedAppLogicAction](./serializedapplogicaction-definitions-serializedaddusedtimeactionversion2-properties-i.md) – `https://timelimit.io/SerializedAppLogicAction#/definitions/SerializedAddUsedTimeActionVersion2/properties/i`
|
* [Untitled array in SerializedAppLogicAction](./serializedapplogicaction-definitions-serializedaddusedtimeactionversion2-properties-i.md) – `https://timelimit.io/SerializedAppLogicAction#/definitions/SerializedAddUsedTimeActionVersion2/properties/i`
|
||||||
|
|||||||
@@ -9,6 +9,9 @@
|
|||||||
},
|
},
|
||||||
"deviceName": {
|
"deviceName": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
|
},
|
||||||
|
"clientLevel": {
|
||||||
|
"type": "number"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"additionalProperties": false,
|
"additionalProperties": false,
|
||||||
|
|||||||
@@ -545,6 +545,12 @@
|
|||||||
},
|
},
|
||||||
"addedManipulationFlags": {
|
"addedManipulationFlags": {
|
||||||
"type": "number"
|
"type": "number"
|
||||||
|
},
|
||||||
|
"platformType": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"platformLevel": {
|
||||||
|
"type": "number"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"additionalProperties": false,
|
"additionalProperties": false,
|
||||||
|
|||||||
@@ -410,6 +410,9 @@
|
|||||||
},
|
},
|
||||||
"perDay": {
|
"perDay": {
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"e": {
|
||||||
|
"type": "number"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"additionalProperties": false,
|
"additionalProperties": false,
|
||||||
@@ -1458,6 +1461,9 @@
|
|||||||
},
|
},
|
||||||
"perDay": {
|
"perDay": {
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"e": {
|
||||||
|
"type": "number"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"additionalProperties": false,
|
"additionalProperties": false,
|
||||||
|
|||||||
@@ -212,6 +212,12 @@
|
|||||||
},
|
},
|
||||||
"pk": {
|
"pk": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
|
},
|
||||||
|
"pType": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"pLevel": {
|
||||||
|
"type": "number"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"additionalProperties": false,
|
"additionalProperties": false,
|
||||||
@@ -240,6 +246,7 @@
|
|||||||
"model",
|
"model",
|
||||||
"name",
|
"name",
|
||||||
"networkTime",
|
"networkTime",
|
||||||
|
"pLevel",
|
||||||
"qOrLater",
|
"qOrLater",
|
||||||
"reboot",
|
"reboot",
|
||||||
"rebootIsManipulation",
|
"rebootIsManipulation",
|
||||||
|
|||||||
@@ -9,6 +9,9 @@
|
|||||||
},
|
},
|
||||||
"deviceName": {
|
"deviceName": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
|
},
|
||||||
|
"clientLevel": {
|
||||||
|
"type": "number"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"additionalProperties": false,
|
"additionalProperties": false,
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
# Untitled number in CreateFamilyByMailTokenRequest Schema
|
||||||
|
|
||||||
|
```txt
|
||||||
|
https://timelimit.io/CreateFamilyByMailTokenRequest#/properties/clientLevel
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
| Abstract | Extensible | Status | Identifiable | Custom Properties | Additional Properties | Access Restrictions | Defined In |
|
||||||
|
| :------------------ | :--------- | :------------- | :---------------------- | :---------------- | :-------------------- | :------------------ | :---------------------------------------------------------------------------------------------------------------- |
|
||||||
|
| Can be instantiated | No | Unknown status | Unknown identifiability | Forbidden | Allowed | none | [CreateFamilyByMailTokenRequest.schema.json\*](CreateFamilyByMailTokenRequest.schema.json "open original schema") |
|
||||||
|
|
||||||
|
## clientLevel Type
|
||||||
|
|
||||||
|
`number`
|
||||||
@@ -24,6 +24,7 @@ https://timelimit.io/CreateFamilyByMailTokenRequest
|
|||||||
| [deviceName](#devicename) | `string` | Required | cannot be null | [CreateFamilyByMailTokenRequest](createfamilybymailtokenrequest-properties-devicename.md "https://timelimit.io/CreateFamilyByMailTokenRequest#/properties/deviceName") |
|
| [deviceName](#devicename) | `string` | Required | cannot be null | [CreateFamilyByMailTokenRequest](createfamilybymailtokenrequest-properties-devicename.md "https://timelimit.io/CreateFamilyByMailTokenRequest#/properties/deviceName") |
|
||||||
| [timeZone](#timezone) | `string` | Required | cannot be null | [CreateFamilyByMailTokenRequest](createfamilybymailtokenrequest-properties-timezone.md "https://timelimit.io/CreateFamilyByMailTokenRequest#/properties/timeZone") |
|
| [timeZone](#timezone) | `string` | Required | cannot be null | [CreateFamilyByMailTokenRequest](createfamilybymailtokenrequest-properties-timezone.md "https://timelimit.io/CreateFamilyByMailTokenRequest#/properties/timeZone") |
|
||||||
| [parentName](#parentname) | `string` | Required | cannot be null | [CreateFamilyByMailTokenRequest](createfamilybymailtokenrequest-properties-parentname.md "https://timelimit.io/CreateFamilyByMailTokenRequest#/properties/parentName") |
|
| [parentName](#parentname) | `string` | Required | cannot be null | [CreateFamilyByMailTokenRequest](createfamilybymailtokenrequest-properties-parentname.md "https://timelimit.io/CreateFamilyByMailTokenRequest#/properties/parentName") |
|
||||||
|
| [clientLevel](#clientlevel) | `number` | Optional | cannot be null | [CreateFamilyByMailTokenRequest](createfamilybymailtokenrequest-properties-clientlevel.md "https://timelimit.io/CreateFamilyByMailTokenRequest#/properties/clientLevel") |
|
||||||
|
|
||||||
## mailAuthToken
|
## mailAuthToken
|
||||||
|
|
||||||
@@ -133,6 +134,24 @@ https://timelimit.io/CreateFamilyByMailTokenRequest
|
|||||||
|
|
||||||
`string`
|
`string`
|
||||||
|
|
||||||
|
## clientLevel
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
`clientLevel`
|
||||||
|
|
||||||
|
* is optional
|
||||||
|
|
||||||
|
* Type: `number`
|
||||||
|
|
||||||
|
* cannot be null
|
||||||
|
|
||||||
|
* defined in: [CreateFamilyByMailTokenRequest](createfamilybymailtokenrequest-properties-clientlevel.md "https://timelimit.io/CreateFamilyByMailTokenRequest#/properties/clientLevel")
|
||||||
|
|
||||||
|
### clientLevel Type
|
||||||
|
|
||||||
|
`number`
|
||||||
|
|
||||||
# CreateFamilyByMailTokenRequest Definitions
|
# CreateFamilyByMailTokenRequest Definitions
|
||||||
|
|
||||||
## Definitions group PlaintextParentPassword
|
## Definitions group PlaintextParentPassword
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
# Untitled undefined type in DeleteAccountPayload Schema
|
||||||
|
|
||||||
|
```txt
|
||||||
|
https://timelimit.io/DeleteAccountPayload#/definitions
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
| Abstract | Extensible | Status | Identifiable | Custom Properties | Additional Properties | Access Restrictions | Defined In |
|
||||||
|
| :------------------ | :--------- | :------------- | :---------------------- | :---------------- | :-------------------- | :------------------ | :-------------------------------------------------------------------------------------------- |
|
||||||
|
| Can be instantiated | No | Unknown status | Unknown identifiability | Forbidden | Allowed | none | [DeleteAccountPayload.schema.json\*](DeleteAccountPayload.schema.json "open original schema") |
|
||||||
|
|
||||||
|
## definitions Type
|
||||||
|
|
||||||
|
unknown
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
# Untitled string in DeleteAccountPayload Schema
|
||||||
|
|
||||||
|
```txt
|
||||||
|
https://timelimit.io/DeleteAccountPayload#/properties/deviceAuthToken
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
| Abstract | Extensible | Status | Identifiable | Custom Properties | Additional Properties | Access Restrictions | Defined In |
|
||||||
|
| :------------------ | :--------- | :------------- | :---------------------- | :---------------- | :-------------------- | :------------------ | :-------------------------------------------------------------------------------------------- |
|
||||||
|
| Can be instantiated | No | Unknown status | Unknown identifiability | Forbidden | Allowed | none | [DeleteAccountPayload.schema.json\*](DeleteAccountPayload.schema.json "open original schema") |
|
||||||
|
|
||||||
|
## deviceAuthToken Type
|
||||||
|
|
||||||
|
`string`
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
# Untitled string in DeleteAccountPayload Schema
|
||||||
|
|
||||||
|
```txt
|
||||||
|
https://timelimit.io/DeleteAccountPayload#/properties/mailAuthTokens/items
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
| Abstract | Extensible | Status | Identifiable | Custom Properties | Additional Properties | Access Restrictions | Defined In |
|
||||||
|
| :------------------ | :--------- | :------------- | :---------------------- | :---------------- | :-------------------- | :------------------ | :-------------------------------------------------------------------------------------------- |
|
||||||
|
| Can be instantiated | No | Unknown status | Unknown identifiability | Forbidden | Allowed | none | [DeleteAccountPayload.schema.json\*](DeleteAccountPayload.schema.json "open original schema") |
|
||||||
|
|
||||||
|
## items Type
|
||||||
|
|
||||||
|
`string`
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
# Untitled array in DeleteAccountPayload Schema
|
||||||
|
|
||||||
|
```txt
|
||||||
|
https://timelimit.io/DeleteAccountPayload#/properties/mailAuthTokens
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
| Abstract | Extensible | Status | Identifiable | Custom Properties | Additional Properties | Access Restrictions | Defined In |
|
||||||
|
| :------------------ | :--------- | :------------- | :---------------------- | :---------------- | :-------------------- | :------------------ | :-------------------------------------------------------------------------------------------- |
|
||||||
|
| Can be instantiated | No | Unknown status | Unknown identifiability | Forbidden | Allowed | none | [DeleteAccountPayload.schema.json\*](DeleteAccountPayload.schema.json "open original schema") |
|
||||||
|
|
||||||
|
## mailAuthTokens Type
|
||||||
|
|
||||||
|
`string[]`
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
# DeleteAccountPayload Schema
|
||||||
|
|
||||||
|
```txt
|
||||||
|
https://timelimit.io/DeleteAccountPayload
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
| Abstract | Extensible | Status | Identifiable | Custom Properties | Additional Properties | Access Restrictions | Defined In |
|
||||||
|
| :------------------ | :--------- | :------------- | :----------- | :---------------- | :-------------------- | :------------------ | :------------------------------------------------------------------------------------------ |
|
||||||
|
| Can be instantiated | Yes | Unknown status | No | Forbidden | Forbidden | none | [DeleteAccountPayload.schema.json](DeleteAccountPayload.schema.json "open original schema") |
|
||||||
|
|
||||||
|
## DeleteAccountPayload Type
|
||||||
|
|
||||||
|
`object` ([DeleteAccountPayload](deleteaccountpayload.md))
|
||||||
|
|
||||||
|
# DeleteAccountPayload Properties
|
||||||
|
|
||||||
|
| Property | Type | Required | Nullable | Defined by |
|
||||||
|
| :---------------------------------- | :------- | :------- | :------------- | :------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
|
| [deviceAuthToken](#deviceauthtoken) | `string` | Required | cannot be null | [DeleteAccountPayload](deleteaccountpayload-properties-deviceauthtoken.md "https://timelimit.io/DeleteAccountPayload#/properties/deviceAuthToken") |
|
||||||
|
| [mailAuthTokens](#mailauthtokens) | `array` | Required | cannot be null | [DeleteAccountPayload](deleteaccountpayload-properties-mailauthtokens.md "https://timelimit.io/DeleteAccountPayload#/properties/mailAuthTokens") |
|
||||||
|
|
||||||
|
## deviceAuthToken
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
`deviceAuthToken`
|
||||||
|
|
||||||
|
* is required
|
||||||
|
|
||||||
|
* Type: `string`
|
||||||
|
|
||||||
|
* cannot be null
|
||||||
|
|
||||||
|
* defined in: [DeleteAccountPayload](deleteaccountpayload-properties-deviceauthtoken.md "https://timelimit.io/DeleteAccountPayload#/properties/deviceAuthToken")
|
||||||
|
|
||||||
|
### deviceAuthToken Type
|
||||||
|
|
||||||
|
`string`
|
||||||
|
|
||||||
|
## mailAuthTokens
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
`mailAuthTokens`
|
||||||
|
|
||||||
|
* is required
|
||||||
|
|
||||||
|
* Type: `string[]`
|
||||||
|
|
||||||
|
* cannot be null
|
||||||
|
|
||||||
|
* defined in: [DeleteAccountPayload](deleteaccountpayload-properties-mailauthtokens.md "https://timelimit.io/DeleteAccountPayload#/properties/mailAuthTokens")
|
||||||
|
|
||||||
|
### mailAuthTokens Type
|
||||||
|
|
||||||
|
`string[]`
|
||||||
|
|
||||||
|
# DeleteAccountPayload Definitions
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
# Untitled number in RegisterChildDeviceRequest Schema
|
||||||
|
|
||||||
|
```txt
|
||||||
|
https://timelimit.io/RegisterChildDeviceRequest#/properties/clientLevel
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
| Abstract | Extensible | Status | Identifiable | Custom Properties | Additional Properties | Access Restrictions | Defined In |
|
||||||
|
| :------------------ | :--------- | :------------- | :---------------------- | :---------------- | :-------------------- | :------------------ | :-------------------------------------------------------------------------------------------------------- |
|
||||||
|
| Can be instantiated | No | Unknown status | Unknown identifiability | Forbidden | Allowed | none | [RegisterChildDeviceRequest.schema.json\*](RegisterChildDeviceRequest.schema.json "open original schema") |
|
||||||
|
|
||||||
|
## clientLevel Type
|
||||||
|
|
||||||
|
`number`
|
||||||
@@ -21,6 +21,7 @@ https://timelimit.io/RegisterChildDeviceRequest
|
|||||||
| [registerToken](#registertoken) | `string` | Required | cannot be null | [RegisterChildDeviceRequest](registerchilddevicerequest-properties-registertoken.md "https://timelimit.io/RegisterChildDeviceRequest#/properties/registerToken") |
|
| [registerToken](#registertoken) | `string` | Required | cannot be null | [RegisterChildDeviceRequest](registerchilddevicerequest-properties-registertoken.md "https://timelimit.io/RegisterChildDeviceRequest#/properties/registerToken") |
|
||||||
| [childDevice](#childdevice) | `object` | Required | cannot be null | [RegisterChildDeviceRequest](registerchilddevicerequest-definitions-newdeviceinfo.md "https://timelimit.io/RegisterChildDeviceRequest#/properties/childDevice") |
|
| [childDevice](#childdevice) | `object` | Required | cannot be null | [RegisterChildDeviceRequest](registerchilddevicerequest-definitions-newdeviceinfo.md "https://timelimit.io/RegisterChildDeviceRequest#/properties/childDevice") |
|
||||||
| [deviceName](#devicename) | `string` | Required | cannot be null | [RegisterChildDeviceRequest](registerchilddevicerequest-properties-devicename.md "https://timelimit.io/RegisterChildDeviceRequest#/properties/deviceName") |
|
| [deviceName](#devicename) | `string` | Required | cannot be null | [RegisterChildDeviceRequest](registerchilddevicerequest-properties-devicename.md "https://timelimit.io/RegisterChildDeviceRequest#/properties/deviceName") |
|
||||||
|
| [clientLevel](#clientlevel) | `number` | Optional | cannot be null | [RegisterChildDeviceRequest](registerchilddevicerequest-properties-clientlevel.md "https://timelimit.io/RegisterChildDeviceRequest#/properties/clientLevel") |
|
||||||
|
|
||||||
## registerToken
|
## registerToken
|
||||||
|
|
||||||
@@ -76,6 +77,24 @@ https://timelimit.io/RegisterChildDeviceRequest
|
|||||||
|
|
||||||
`string`
|
`string`
|
||||||
|
|
||||||
|
## clientLevel
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
`clientLevel`
|
||||||
|
|
||||||
|
* is optional
|
||||||
|
|
||||||
|
* Type: `number`
|
||||||
|
|
||||||
|
* cannot be null
|
||||||
|
|
||||||
|
* defined in: [RegisterChildDeviceRequest](registerchilddevicerequest-properties-clientlevel.md "https://timelimit.io/RegisterChildDeviceRequest#/properties/clientLevel")
|
||||||
|
|
||||||
|
### clientLevel Type
|
||||||
|
|
||||||
|
`number`
|
||||||
|
|
||||||
# RegisterChildDeviceRequest Definitions
|
# RegisterChildDeviceRequest Definitions
|
||||||
|
|
||||||
## Definitions group NewDeviceInfo
|
## Definitions group NewDeviceInfo
|
||||||
|
|||||||
+15
@@ -0,0 +1,15 @@
|
|||||||
|
# Untitled number in SerializedAppLogicAction Schema
|
||||||
|
|
||||||
|
```txt
|
||||||
|
https://timelimit.io/SerializedAppLogicAction#/definitions/SerializedUpdateDeviceStatusAction/properties/platformLevel
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
| Abstract | Extensible | Status | Identifiable | Custom Properties | Additional Properties | Access Restrictions | Defined In |
|
||||||
|
| :------------------ | :--------- | :------------- | :---------------------- | :---------------- | :-------------------- | :------------------ | :---------------------------------------------------------------------------------------------------- |
|
||||||
|
| Can be instantiated | No | Unknown status | Unknown identifiability | Forbidden | Allowed | none | [SerializedAppLogicAction.schema.json\*](SerializedAppLogicAction.schema.json "open original schema") |
|
||||||
|
|
||||||
|
## platformLevel Type
|
||||||
|
|
||||||
|
`number`
|
||||||
+15
@@ -0,0 +1,15 @@
|
|||||||
|
# Untitled string in SerializedAppLogicAction Schema
|
||||||
|
|
||||||
|
```txt
|
||||||
|
https://timelimit.io/SerializedAppLogicAction#/definitions/SerializedUpdateDeviceStatusAction/properties/platformType
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
| Abstract | Extensible | Status | Identifiable | Custom Properties | Additional Properties | Access Restrictions | Defined In |
|
||||||
|
| :------------------ | :--------- | :------------- | :---------------------- | :---------------- | :-------------------- | :------------------ | :---------------------------------------------------------------------------------------------------- |
|
||||||
|
| Can be instantiated | No | Unknown status | Unknown identifiability | Forbidden | Allowed | none | [SerializedAppLogicAction.schema.json\*](SerializedAppLogicAction.schema.json "open original schema") |
|
||||||
|
|
||||||
|
## platformType Type
|
||||||
|
|
||||||
|
`string`
|
||||||
+38
@@ -28,6 +28,8 @@ https://timelimit.io/SerializedAppLogicAction#/definitions/SerializedUpdateDevic
|
|||||||
| [didReboot](#didreboot) | `boolean` | Optional | cannot be null | [SerializedAppLogicAction](serializedapplogicaction-definitions-serializedupdatedevicestatusaction-properties-didreboot.md "https://timelimit.io/SerializedAppLogicAction#/definitions/SerializedUpdateDeviceStatusAction/properties/didReboot") |
|
| [didReboot](#didreboot) | `boolean` | Optional | cannot be null | [SerializedAppLogicAction](serializedapplogicaction-definitions-serializedupdatedevicestatusaction-properties-didreboot.md "https://timelimit.io/SerializedAppLogicAction#/definitions/SerializedUpdateDeviceStatusAction/properties/didReboot") |
|
||||||
| [isQOrLaterNow](#isqorlaternow) | `boolean` | Optional | cannot be null | [SerializedAppLogicAction](serializedapplogicaction-definitions-serializedupdatedevicestatusaction-properties-isqorlaternow.md "https://timelimit.io/SerializedAppLogicAction#/definitions/SerializedUpdateDeviceStatusAction/properties/isQOrLaterNow") |
|
| [isQOrLaterNow](#isqorlaternow) | `boolean` | Optional | cannot be null | [SerializedAppLogicAction](serializedapplogicaction-definitions-serializedupdatedevicestatusaction-properties-isqorlaternow.md "https://timelimit.io/SerializedAppLogicAction#/definitions/SerializedUpdateDeviceStatusAction/properties/isQOrLaterNow") |
|
||||||
| [addedManipulationFlags](#addedmanipulationflags) | `number` | Optional | cannot be null | [SerializedAppLogicAction](serializedapplogicaction-definitions-serializedupdatedevicestatusaction-properties-addedmanipulationflags.md "https://timelimit.io/SerializedAppLogicAction#/definitions/SerializedUpdateDeviceStatusAction/properties/addedManipulationFlags") |
|
| [addedManipulationFlags](#addedmanipulationflags) | `number` | Optional | cannot be null | [SerializedAppLogicAction](serializedapplogicaction-definitions-serializedupdatedevicestatusaction-properties-addedmanipulationflags.md "https://timelimit.io/SerializedAppLogicAction#/definitions/SerializedUpdateDeviceStatusAction/properties/addedManipulationFlags") |
|
||||||
|
| [platformType](#platformtype) | `string` | Optional | cannot be null | [SerializedAppLogicAction](serializedapplogicaction-definitions-serializedupdatedevicestatusaction-properties-platformtype.md "https://timelimit.io/SerializedAppLogicAction#/definitions/SerializedUpdateDeviceStatusAction/properties/platformType") |
|
||||||
|
| [platformLevel](#platformlevel) | `number` | Optional | cannot be null | [SerializedAppLogicAction](serializedapplogicaction-definitions-serializedupdatedevicestatusaction-properties-platformlevel.md "https://timelimit.io/SerializedAppLogicAction#/definitions/SerializedUpdateDeviceStatusAction/properties/platformLevel") |
|
||||||
|
|
||||||
## type
|
## type
|
||||||
|
|
||||||
@@ -257,3 +259,39 @@ https://timelimit.io/SerializedAppLogicAction#/definitions/SerializedUpdateDevic
|
|||||||
### addedManipulationFlags Type
|
### addedManipulationFlags Type
|
||||||
|
|
||||||
`number`
|
`number`
|
||||||
|
|
||||||
|
## platformType
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
`platformType`
|
||||||
|
|
||||||
|
* is optional
|
||||||
|
|
||||||
|
* Type: `string`
|
||||||
|
|
||||||
|
* cannot be null
|
||||||
|
|
||||||
|
* defined in: [SerializedAppLogicAction](serializedapplogicaction-definitions-serializedupdatedevicestatusaction-properties-platformtype.md "https://timelimit.io/SerializedAppLogicAction#/definitions/SerializedUpdateDeviceStatusAction/properties/platformType")
|
||||||
|
|
||||||
|
### platformType Type
|
||||||
|
|
||||||
|
`string`
|
||||||
|
|
||||||
|
## platformLevel
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
`platformLevel`
|
||||||
|
|
||||||
|
* is optional
|
||||||
|
|
||||||
|
* Type: `number`
|
||||||
|
|
||||||
|
* cannot be null
|
||||||
|
|
||||||
|
* defined in: [SerializedAppLogicAction](serializedapplogicaction-definitions-serializedupdatedevicestatusaction-properties-platformlevel.md "https://timelimit.io/SerializedAppLogicAction#/definitions/SerializedUpdateDeviceStatusAction/properties/platformLevel")
|
||||||
|
|
||||||
|
### platformLevel Type
|
||||||
|
|
||||||
|
`number`
|
||||||
|
|||||||
@@ -1240,6 +1240,8 @@ Reference this group by using
|
|||||||
| [didReboot](#didreboot) | `boolean` | Optional | cannot be null | [SerializedAppLogicAction](serializedapplogicaction-definitions-serializedupdatedevicestatusaction-properties-didreboot.md "https://timelimit.io/SerializedAppLogicAction#/definitions/SerializedUpdateDeviceStatusAction/properties/didReboot") |
|
| [didReboot](#didreboot) | `boolean` | Optional | cannot be null | [SerializedAppLogicAction](serializedapplogicaction-definitions-serializedupdatedevicestatusaction-properties-didreboot.md "https://timelimit.io/SerializedAppLogicAction#/definitions/SerializedUpdateDeviceStatusAction/properties/didReboot") |
|
||||||
| [isQOrLaterNow](#isqorlaternow) | `boolean` | Optional | cannot be null | [SerializedAppLogicAction](serializedapplogicaction-definitions-serializedupdatedevicestatusaction-properties-isqorlaternow.md "https://timelimit.io/SerializedAppLogicAction#/definitions/SerializedUpdateDeviceStatusAction/properties/isQOrLaterNow") |
|
| [isQOrLaterNow](#isqorlaternow) | `boolean` | Optional | cannot be null | [SerializedAppLogicAction](serializedapplogicaction-definitions-serializedupdatedevicestatusaction-properties-isqorlaternow.md "https://timelimit.io/SerializedAppLogicAction#/definitions/SerializedUpdateDeviceStatusAction/properties/isQOrLaterNow") |
|
||||||
| [addedManipulationFlags](#addedmanipulationflags) | `number` | Optional | cannot be null | [SerializedAppLogicAction](serializedapplogicaction-definitions-serializedupdatedevicestatusaction-properties-addedmanipulationflags.md "https://timelimit.io/SerializedAppLogicAction#/definitions/SerializedUpdateDeviceStatusAction/properties/addedManipulationFlags") |
|
| [addedManipulationFlags](#addedmanipulationflags) | `number` | Optional | cannot be null | [SerializedAppLogicAction](serializedapplogicaction-definitions-serializedupdatedevicestatusaction-properties-addedmanipulationflags.md "https://timelimit.io/SerializedAppLogicAction#/definitions/SerializedUpdateDeviceStatusAction/properties/addedManipulationFlags") |
|
||||||
|
| [platformType](#platformtype) | `string` | Optional | cannot be null | [SerializedAppLogicAction](serializedapplogicaction-definitions-serializedupdatedevicestatusaction-properties-platformtype.md "https://timelimit.io/SerializedAppLogicAction#/definitions/SerializedUpdateDeviceStatusAction/properties/platformType") |
|
||||||
|
| [platformLevel](#platformlevel) | `number` | Optional | cannot be null | [SerializedAppLogicAction](serializedapplogicaction-definitions-serializedupdatedevicestatusaction-properties-platformlevel.md "https://timelimit.io/SerializedAppLogicAction#/definitions/SerializedUpdateDeviceStatusAction/properties/platformLevel") |
|
||||||
|
|
||||||
### type
|
### type
|
||||||
|
|
||||||
@@ -1470,6 +1472,42 @@ Reference this group by using
|
|||||||
|
|
||||||
`number`
|
`number`
|
||||||
|
|
||||||
|
### platformType
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
`platformType`
|
||||||
|
|
||||||
|
* is optional
|
||||||
|
|
||||||
|
* Type: `string`
|
||||||
|
|
||||||
|
* cannot be null
|
||||||
|
|
||||||
|
* defined in: [SerializedAppLogicAction](serializedapplogicaction-definitions-serializedupdatedevicestatusaction-properties-platformtype.md "https://timelimit.io/SerializedAppLogicAction#/definitions/SerializedUpdateDeviceStatusAction/properties/platformType")
|
||||||
|
|
||||||
|
#### platformType Type
|
||||||
|
|
||||||
|
`string`
|
||||||
|
|
||||||
|
### platformLevel
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
`platformLevel`
|
||||||
|
|
||||||
|
* is optional
|
||||||
|
|
||||||
|
* Type: `number`
|
||||||
|
|
||||||
|
* cannot be null
|
||||||
|
|
||||||
|
* defined in: [SerializedAppLogicAction](serializedapplogicaction-definitions-serializedupdatedevicestatusaction-properties-platformlevel.md "https://timelimit.io/SerializedAppLogicAction#/definitions/SerializedUpdateDeviceStatusAction/properties/platformLevel")
|
||||||
|
|
||||||
|
#### platformLevel Type
|
||||||
|
|
||||||
|
`number`
|
||||||
|
|
||||||
## Definitions group SerializedUploadDevicePublicKeyAction
|
## Definitions group SerializedUploadDevicePublicKeyAction
|
||||||
|
|
||||||
Reference this group by using
|
Reference this group by using
|
||||||
|
|||||||
+15
@@ -0,0 +1,15 @@
|
|||||||
|
# Untitled number in SerializedParentAction Schema
|
||||||
|
|
||||||
|
```txt
|
||||||
|
https://timelimit.io/SerializedParentAction#/definitions/SerializedTimeLimitRule/properties/e
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
| Abstract | Extensible | Status | Identifiable | Custom Properties | Additional Properties | Access Restrictions | Defined In |
|
||||||
|
| :------------------ | :--------- | :------------- | :---------------------- | :---------------- | :-------------------- | :------------------ | :------------------------------------------------------------------------------------------------ |
|
||||||
|
| Can be instantiated | No | Unknown status | Unknown identifiability | Forbidden | Allowed | none | [SerializedParentAction.schema.json\*](SerializedParentAction.schema.json "open original schema") |
|
||||||
|
|
||||||
|
## e Type
|
||||||
|
|
||||||
|
`number`
|
||||||
@@ -28,6 +28,7 @@ https://timelimit.io/SerializedParentAction#/definitions/SerializedTimeLimitRule
|
|||||||
| [dur](#dur) | `number` | Optional | cannot be null | [SerializedParentAction](serializedparentaction-definitions-serializedtimelimitrule-properties-dur.md "https://timelimit.io/SerializedParentAction#/definitions/SerializedTimeLimitRule/properties/dur") |
|
| [dur](#dur) | `number` | Optional | cannot be null | [SerializedParentAction](serializedparentaction-definitions-serializedtimelimitrule-properties-dur.md "https://timelimit.io/SerializedParentAction#/definitions/SerializedTimeLimitRule/properties/dur") |
|
||||||
| [pause](#pause) | `number` | Optional | cannot be null | [SerializedParentAction](serializedparentaction-definitions-serializedtimelimitrule-properties-pause.md "https://timelimit.io/SerializedParentAction#/definitions/SerializedTimeLimitRule/properties/pause") |
|
| [pause](#pause) | `number` | Optional | cannot be null | [SerializedParentAction](serializedparentaction-definitions-serializedtimelimitrule-properties-pause.md "https://timelimit.io/SerializedParentAction#/definitions/SerializedTimeLimitRule/properties/pause") |
|
||||||
| [perDay](#perday) | `boolean` | Optional | cannot be null | [SerializedParentAction](serializedparentaction-definitions-serializedtimelimitrule-properties-perday.md "https://timelimit.io/SerializedParentAction#/definitions/SerializedTimeLimitRule/properties/perDay") |
|
| [perDay](#perday) | `boolean` | Optional | cannot be null | [SerializedParentAction](serializedparentaction-definitions-serializedtimelimitrule-properties-perday.md "https://timelimit.io/SerializedParentAction#/definitions/SerializedTimeLimitRule/properties/perDay") |
|
||||||
|
| [e](#e) | `number` | Optional | cannot be null | [SerializedParentAction](serializedparentaction-definitions-serializedtimelimitrule-properties-e.md "https://timelimit.io/SerializedParentAction#/definitions/SerializedTimeLimitRule/properties/e") |
|
||||||
|
|
||||||
## ruleId
|
## ruleId
|
||||||
|
|
||||||
@@ -208,3 +209,21 @@ https://timelimit.io/SerializedParentAction#/definitions/SerializedTimeLimitRule
|
|||||||
### perDay Type
|
### perDay Type
|
||||||
|
|
||||||
`boolean`
|
`boolean`
|
||||||
|
|
||||||
|
## e
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
`e`
|
||||||
|
|
||||||
|
* is optional
|
||||||
|
|
||||||
|
* Type: `number`
|
||||||
|
|
||||||
|
* cannot be null
|
||||||
|
|
||||||
|
* defined in: [SerializedParentAction](serializedparentaction-definitions-serializedtimelimitrule-properties-e.md "https://timelimit.io/SerializedParentAction#/definitions/SerializedTimeLimitRule/properties/e")
|
||||||
|
|
||||||
|
### e Type
|
||||||
|
|
||||||
|
`number`
|
||||||
|
|||||||
+15
@@ -0,0 +1,15 @@
|
|||||||
|
# Untitled number in SerializedParentAction Schema
|
||||||
|
|
||||||
|
```txt
|
||||||
|
https://timelimit.io/SerializedParentAction#/definitions/SerializedUpdateTimelimitRuleAction/properties/e
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
| Abstract | Extensible | Status | Identifiable | Custom Properties | Additional Properties | Access Restrictions | Defined In |
|
||||||
|
| :------------------ | :--------- | :------------- | :---------------------- | :---------------- | :-------------------- | :------------------ | :------------------------------------------------------------------------------------------------ |
|
||||||
|
| Can be instantiated | No | Unknown status | Unknown identifiability | Forbidden | Allowed | none | [SerializedParentAction.schema.json\*](SerializedParentAction.schema.json "open original schema") |
|
||||||
|
|
||||||
|
## e Type
|
||||||
|
|
||||||
|
`number`
|
||||||
+15
@@ -0,0 +1,15 @@
|
|||||||
|
# Untitled number in SerializedParentAction Schema
|
||||||
|
|
||||||
|
```txt
|
||||||
|
https://timelimit.io/SerializedParentAction#/definitions/SerializedUpdateTimelimitRuleAction/properties/expiresAt
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
| Abstract | Extensible | Status | Identifiable | Custom Properties | Additional Properties | Access Restrictions | Defined In |
|
||||||
|
| :------------------ | :--------- | :------------- | :---------------------- | :---------------- | :-------------------- | :------------------ | :------------------------------------------------------------------------------------------------ |
|
||||||
|
| Can be instantiated | No | Unknown status | Unknown identifiability | Forbidden | Allowed | none | [SerializedParentAction.schema.json\*](SerializedParentAction.schema.json "open original schema") |
|
||||||
|
|
||||||
|
## expiresAt Type
|
||||||
|
|
||||||
|
`number`
|
||||||
@@ -28,6 +28,7 @@ https://timelimit.io/SerializedParentAction#/definitions/SerializedUpdateTimelim
|
|||||||
| [dur](#dur) | `number` | Optional | cannot be null | [SerializedParentAction](serializedparentaction-definitions-serializedupdatetimelimitruleaction-properties-dur.md "https://timelimit.io/SerializedParentAction#/definitions/SerializedUpdateTimelimitRuleAction/properties/dur") |
|
| [dur](#dur) | `number` | Optional | cannot be null | [SerializedParentAction](serializedparentaction-definitions-serializedupdatetimelimitruleaction-properties-dur.md "https://timelimit.io/SerializedParentAction#/definitions/SerializedUpdateTimelimitRuleAction/properties/dur") |
|
||||||
| [pause](#pause) | `number` | Optional | cannot be null | [SerializedParentAction](serializedparentaction-definitions-serializedupdatetimelimitruleaction-properties-pause.md "https://timelimit.io/SerializedParentAction#/definitions/SerializedUpdateTimelimitRuleAction/properties/pause") |
|
| [pause](#pause) | `number` | Optional | cannot be null | [SerializedParentAction](serializedparentaction-definitions-serializedupdatetimelimitruleaction-properties-pause.md "https://timelimit.io/SerializedParentAction#/definitions/SerializedUpdateTimelimitRuleAction/properties/pause") |
|
||||||
| [perDay](#perday) | `boolean` | Optional | cannot be null | [SerializedParentAction](serializedparentaction-definitions-serializedupdatetimelimitruleaction-properties-perday.md "https://timelimit.io/SerializedParentAction#/definitions/SerializedUpdateTimelimitRuleAction/properties/perDay") |
|
| [perDay](#perday) | `boolean` | Optional | cannot be null | [SerializedParentAction](serializedparentaction-definitions-serializedupdatetimelimitruleaction-properties-perday.md "https://timelimit.io/SerializedParentAction#/definitions/SerializedUpdateTimelimitRuleAction/properties/perDay") |
|
||||||
|
| [e](#e) | `number` | Optional | cannot be null | [SerializedParentAction](serializedparentaction-definitions-serializedupdatetimelimitruleaction-properties-e.md "https://timelimit.io/SerializedParentAction#/definitions/SerializedUpdateTimelimitRuleAction/properties/e") |
|
||||||
|
|
||||||
## type
|
## type
|
||||||
|
|
||||||
@@ -216,3 +217,21 @@ https://timelimit.io/SerializedParentAction#/definitions/SerializedUpdateTimelim
|
|||||||
### perDay Type
|
### perDay Type
|
||||||
|
|
||||||
`boolean`
|
`boolean`
|
||||||
|
|
||||||
|
## e
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
`e`
|
||||||
|
|
||||||
|
* is optional
|
||||||
|
|
||||||
|
* Type: `number`
|
||||||
|
|
||||||
|
* cannot be null
|
||||||
|
|
||||||
|
* defined in: [SerializedParentAction](serializedparentaction-definitions-serializedupdatetimelimitruleaction-properties-e.md "https://timelimit.io/SerializedParentAction#/definitions/SerializedUpdateTimelimitRuleAction/properties/e")
|
||||||
|
|
||||||
|
### e Type
|
||||||
|
|
||||||
|
`number`
|
||||||
|
|||||||
@@ -899,6 +899,7 @@ Reference this group by using
|
|||||||
| [dur](#dur) | `number` | Optional | cannot be null | [SerializedParentAction](serializedparentaction-definitions-serializedtimelimitrule-properties-dur.md "https://timelimit.io/SerializedParentAction#/definitions/SerializedTimeLimitRule/properties/dur") |
|
| [dur](#dur) | `number` | Optional | cannot be null | [SerializedParentAction](serializedparentaction-definitions-serializedtimelimitrule-properties-dur.md "https://timelimit.io/SerializedParentAction#/definitions/SerializedTimeLimitRule/properties/dur") |
|
||||||
| [pause](#pause) | `number` | Optional | cannot be null | [SerializedParentAction](serializedparentaction-definitions-serializedtimelimitrule-properties-pause.md "https://timelimit.io/SerializedParentAction#/definitions/SerializedTimeLimitRule/properties/pause") |
|
| [pause](#pause) | `number` | Optional | cannot be null | [SerializedParentAction](serializedparentaction-definitions-serializedtimelimitrule-properties-pause.md "https://timelimit.io/SerializedParentAction#/definitions/SerializedTimeLimitRule/properties/pause") |
|
||||||
| [perDay](#perday) | `boolean` | Optional | cannot be null | [SerializedParentAction](serializedparentaction-definitions-serializedtimelimitrule-properties-perday.md "https://timelimit.io/SerializedParentAction#/definitions/SerializedTimeLimitRule/properties/perDay") |
|
| [perDay](#perday) | `boolean` | Optional | cannot be null | [SerializedParentAction](serializedparentaction-definitions-serializedtimelimitrule-properties-perday.md "https://timelimit.io/SerializedParentAction#/definitions/SerializedTimeLimitRule/properties/perDay") |
|
||||||
|
| [e](#e) | `number` | Optional | cannot be null | [SerializedParentAction](serializedparentaction-definitions-serializedtimelimitrule-properties-e.md "https://timelimit.io/SerializedParentAction#/definitions/SerializedTimeLimitRule/properties/e") |
|
||||||
|
|
||||||
### ruleId
|
### ruleId
|
||||||
|
|
||||||
@@ -1080,6 +1081,24 @@ Reference this group by using
|
|||||||
|
|
||||||
`boolean`
|
`boolean`
|
||||||
|
|
||||||
|
### e
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
`e`
|
||||||
|
|
||||||
|
* is optional
|
||||||
|
|
||||||
|
* Type: `number`
|
||||||
|
|
||||||
|
* cannot be null
|
||||||
|
|
||||||
|
* defined in: [SerializedParentAction](serializedparentaction-definitions-serializedtimelimitrule-properties-e.md "https://timelimit.io/SerializedParentAction#/definitions/SerializedTimeLimitRule/properties/e")
|
||||||
|
|
||||||
|
#### e Type
|
||||||
|
|
||||||
|
`number`
|
||||||
|
|
||||||
## Definitions group SerializedDeleteCategoryAction
|
## Definitions group SerializedDeleteCategoryAction
|
||||||
|
|
||||||
Reference this group by using
|
Reference this group by using
|
||||||
@@ -4397,6 +4416,7 @@ Reference this group by using
|
|||||||
| [dur](#dur-1) | `number` | Optional | cannot be null | [SerializedParentAction](serializedparentaction-definitions-serializedupdatetimelimitruleaction-properties-dur.md "https://timelimit.io/SerializedParentAction#/definitions/SerializedUpdateTimelimitRuleAction/properties/dur") |
|
| [dur](#dur-1) | `number` | Optional | cannot be null | [SerializedParentAction](serializedparentaction-definitions-serializedupdatetimelimitruleaction-properties-dur.md "https://timelimit.io/SerializedParentAction#/definitions/SerializedUpdateTimelimitRuleAction/properties/dur") |
|
||||||
| [pause](#pause-1) | `number` | Optional | cannot be null | [SerializedParentAction](serializedparentaction-definitions-serializedupdatetimelimitruleaction-properties-pause.md "https://timelimit.io/SerializedParentAction#/definitions/SerializedUpdateTimelimitRuleAction/properties/pause") |
|
| [pause](#pause-1) | `number` | Optional | cannot be null | [SerializedParentAction](serializedparentaction-definitions-serializedupdatetimelimitruleaction-properties-pause.md "https://timelimit.io/SerializedParentAction#/definitions/SerializedUpdateTimelimitRuleAction/properties/pause") |
|
||||||
| [perDay](#perday-1) | `boolean` | Optional | cannot be null | [SerializedParentAction](serializedparentaction-definitions-serializedupdatetimelimitruleaction-properties-perday.md "https://timelimit.io/SerializedParentAction#/definitions/SerializedUpdateTimelimitRuleAction/properties/perDay") |
|
| [perDay](#perday-1) | `boolean` | Optional | cannot be null | [SerializedParentAction](serializedparentaction-definitions-serializedupdatetimelimitruleaction-properties-perday.md "https://timelimit.io/SerializedParentAction#/definitions/SerializedUpdateTimelimitRuleAction/properties/perDay") |
|
||||||
|
| [e](#e-1) | `number` | Optional | cannot be null | [SerializedParentAction](serializedparentaction-definitions-serializedupdatetimelimitruleaction-properties-e.md "https://timelimit.io/SerializedParentAction#/definitions/SerializedUpdateTimelimitRuleAction/properties/e") |
|
||||||
|
|
||||||
### type
|
### type
|
||||||
|
|
||||||
@@ -4586,6 +4606,24 @@ Reference this group by using
|
|||||||
|
|
||||||
`boolean`
|
`boolean`
|
||||||
|
|
||||||
|
### e
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
`e`
|
||||||
|
|
||||||
|
* is optional
|
||||||
|
|
||||||
|
* Type: `number`
|
||||||
|
|
||||||
|
* cannot be null
|
||||||
|
|
||||||
|
* defined in: [SerializedParentAction](serializedparentaction-definitions-serializedupdatetimelimitruleaction-properties-e.md "https://timelimit.io/SerializedParentAction#/definitions/SerializedUpdateTimelimitRuleAction/properties/e")
|
||||||
|
|
||||||
|
#### e Type
|
||||||
|
|
||||||
|
`number`
|
||||||
|
|
||||||
## Definitions group SerializedUpdateUserFlagsAction
|
## Definitions group SerializedUpdateUserFlagsAction
|
||||||
|
|
||||||
Reference this group by using
|
Reference this group by using
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
# Untitled number in ServerDataStatus Schema
|
||||||
|
|
||||||
|
```txt
|
||||||
|
https://timelimit.io/ServerDataStatus#/definitions/ServerDeviceData/properties/pLevel
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
| Abstract | Extensible | Status | Identifiable | Custom Properties | Additional Properties | Access Restrictions | Defined In |
|
||||||
|
| :------------------ | :--------- | :------------- | :---------------------- | :---------------- | :-------------------- | :------------------ | :------------------------------------------------------------------------------------ |
|
||||||
|
| Can be instantiated | No | Unknown status | Unknown identifiability | Forbidden | Allowed | none | [ServerDataStatus.schema.json\*](ServerDataStatus.schema.json "open original schema") |
|
||||||
|
|
||||||
|
## pLevel Type
|
||||||
|
|
||||||
|
`number`
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
# Untitled string in ServerDataStatus Schema
|
||||||
|
|
||||||
|
```txt
|
||||||
|
https://timelimit.io/ServerDataStatus#/definitions/ServerDeviceData/properties/pType
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
| Abstract | Extensible | Status | Identifiable | Custom Properties | Additional Properties | Access Restrictions | Defined In |
|
||||||
|
| :------------------ | :--------- | :------------- | :---------------------- | :---------------- | :-------------------- | :------------------ | :------------------------------------------------------------------------------------ |
|
||||||
|
| Can be instantiated | No | Unknown status | Unknown identifiability | Forbidden | Allowed | none | [ServerDataStatus.schema.json\*](ServerDataStatus.schema.json "open original schema") |
|
||||||
|
|
||||||
|
## pType Type
|
||||||
|
|
||||||
|
`string`
|
||||||
@@ -50,6 +50,8 @@ https://timelimit.io/ServerDataStatus#/definitions/ServerDeviceData
|
|||||||
| [qOrLater](#qorlater) | `boolean` | Required | cannot be null | [ServerDataStatus](serverdatastatus-definitions-serverdevicedata-properties-qorlater.md "https://timelimit.io/ServerDataStatus#/definitions/ServerDeviceData/properties/qOrLater") |
|
| [qOrLater](#qorlater) | `boolean` | Required | cannot be null | [ServerDataStatus](serverdatastatus-definitions-serverdevicedata-properties-qorlater.md "https://timelimit.io/ServerDataStatus#/definitions/ServerDeviceData/properties/qOrLater") |
|
||||||
| [mFlags](#mflags) | `number` | Required | cannot be null | [ServerDataStatus](serverdatastatus-definitions-serverdevicedata-properties-mflags.md "https://timelimit.io/ServerDataStatus#/definitions/ServerDeviceData/properties/mFlags") |
|
| [mFlags](#mflags) | `number` | Required | cannot be null | [ServerDataStatus](serverdatastatus-definitions-serverdevicedata-properties-mflags.md "https://timelimit.io/ServerDataStatus#/definitions/ServerDeviceData/properties/mFlags") |
|
||||||
| [pk](#pk) | `string` | Optional | cannot be null | [ServerDataStatus](serverdatastatus-definitions-serverdevicedata-properties-pk.md "https://timelimit.io/ServerDataStatus#/definitions/ServerDeviceData/properties/pk") |
|
| [pk](#pk) | `string` | Optional | cannot be null | [ServerDataStatus](serverdatastatus-definitions-serverdevicedata-properties-pk.md "https://timelimit.io/ServerDataStatus#/definitions/ServerDeviceData/properties/pk") |
|
||||||
|
| [pType](#ptype) | `string` | Optional | cannot be null | [ServerDataStatus](serverdatastatus-definitions-serverdevicedata-properties-ptype.md "https://timelimit.io/ServerDataStatus#/definitions/ServerDeviceData/properties/pType") |
|
||||||
|
| [pLevel](#plevel) | `number` | Required | cannot be null | [ServerDataStatus](serverdatastatus-definitions-serverdevicedata-properties-plevel.md "https://timelimit.io/ServerDataStatus#/definitions/ServerDeviceData/properties/pLevel") |
|
||||||
|
|
||||||
## deviceId
|
## deviceId
|
||||||
|
|
||||||
@@ -718,3 +720,39 @@ https://timelimit.io/ServerDataStatus#/definitions/ServerDeviceData
|
|||||||
### pk Type
|
### pk Type
|
||||||
|
|
||||||
`string`
|
`string`
|
||||||
|
|
||||||
|
## pType
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
`pType`
|
||||||
|
|
||||||
|
* is optional
|
||||||
|
|
||||||
|
* Type: `string`
|
||||||
|
|
||||||
|
* cannot be null
|
||||||
|
|
||||||
|
* defined in: [ServerDataStatus](serverdatastatus-definitions-serverdevicedata-properties-ptype.md "https://timelimit.io/ServerDataStatus#/definitions/ServerDeviceData/properties/pType")
|
||||||
|
|
||||||
|
### pType Type
|
||||||
|
|
||||||
|
`string`
|
||||||
|
|
||||||
|
## pLevel
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
`pLevel`
|
||||||
|
|
||||||
|
* is required
|
||||||
|
|
||||||
|
* Type: `number`
|
||||||
|
|
||||||
|
* cannot be null
|
||||||
|
|
||||||
|
* defined in: [ServerDataStatus](serverdatastatus-definitions-serverdevicedata-properties-plevel.md "https://timelimit.io/ServerDataStatus#/definitions/ServerDeviceData/properties/pLevel")
|
||||||
|
|
||||||
|
### pLevel Type
|
||||||
|
|
||||||
|
`number`
|
||||||
|
|||||||
@@ -435,6 +435,8 @@ Reference this group by using
|
|||||||
| [qOrLater](#qorlater) | `boolean` | Required | cannot be null | [ServerDataStatus](serverdatastatus-definitions-serverdevicedata-properties-qorlater.md "https://timelimit.io/ServerDataStatus#/definitions/ServerDeviceData/properties/qOrLater") |
|
| [qOrLater](#qorlater) | `boolean` | Required | cannot be null | [ServerDataStatus](serverdatastatus-definitions-serverdevicedata-properties-qorlater.md "https://timelimit.io/ServerDataStatus#/definitions/ServerDeviceData/properties/qOrLater") |
|
||||||
| [mFlags](#mflags) | `number` | Required | cannot be null | [ServerDataStatus](serverdatastatus-definitions-serverdevicedata-properties-mflags.md "https://timelimit.io/ServerDataStatus#/definitions/ServerDeviceData/properties/mFlags") |
|
| [mFlags](#mflags) | `number` | Required | cannot be null | [ServerDataStatus](serverdatastatus-definitions-serverdevicedata-properties-mflags.md "https://timelimit.io/ServerDataStatus#/definitions/ServerDeviceData/properties/mFlags") |
|
||||||
| [pk](#pk) | `string` | Optional | cannot be null | [ServerDataStatus](serverdatastatus-definitions-serverdevicedata-properties-pk.md "https://timelimit.io/ServerDataStatus#/definitions/ServerDeviceData/properties/pk") |
|
| [pk](#pk) | `string` | Optional | cannot be null | [ServerDataStatus](serverdatastatus-definitions-serverdevicedata-properties-pk.md "https://timelimit.io/ServerDataStatus#/definitions/ServerDeviceData/properties/pk") |
|
||||||
|
| [pType](#ptype) | `string` | Optional | cannot be null | [ServerDataStatus](serverdatastatus-definitions-serverdevicedata-properties-ptype.md "https://timelimit.io/ServerDataStatus#/definitions/ServerDeviceData/properties/pType") |
|
||||||
|
| [pLevel](#plevel) | `number` | Required | cannot be null | [ServerDataStatus](serverdatastatus-definitions-serverdevicedata-properties-plevel.md "https://timelimit.io/ServerDataStatus#/definitions/ServerDeviceData/properties/pLevel") |
|
||||||
|
|
||||||
### deviceId
|
### deviceId
|
||||||
|
|
||||||
@@ -1104,6 +1106,42 @@ Reference this group by using
|
|||||||
|
|
||||||
`string`
|
`string`
|
||||||
|
|
||||||
|
### pType
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
`pType`
|
||||||
|
|
||||||
|
* is optional
|
||||||
|
|
||||||
|
* Type: `string`
|
||||||
|
|
||||||
|
* cannot be null
|
||||||
|
|
||||||
|
* defined in: [ServerDataStatus](serverdatastatus-definitions-serverdevicedata-properties-ptype.md "https://timelimit.io/ServerDataStatus#/definitions/ServerDeviceData/properties/pType")
|
||||||
|
|
||||||
|
#### pType Type
|
||||||
|
|
||||||
|
`string`
|
||||||
|
|
||||||
|
### pLevel
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
`pLevel`
|
||||||
|
|
||||||
|
* is required
|
||||||
|
|
||||||
|
* Type: `number`
|
||||||
|
|
||||||
|
* cannot be null
|
||||||
|
|
||||||
|
* defined in: [ServerDataStatus](serverdatastatus-definitions-serverdevicedata-properties-plevel.md "https://timelimit.io/ServerDataStatus#/definitions/ServerDeviceData/properties/pLevel")
|
||||||
|
|
||||||
|
#### pLevel Type
|
||||||
|
|
||||||
|
`number`
|
||||||
|
|
||||||
## Definitions group ProtectionLevel
|
## Definitions group ProtectionLevel
|
||||||
|
|
||||||
Reference this group by using
|
Reference this group by using
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
# Untitled number in SignIntoFamilyRequest Schema
|
||||||
|
|
||||||
|
```txt
|
||||||
|
https://timelimit.io/SignIntoFamilyRequest#/properties/clientLevel
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
| Abstract | Extensible | Status | Identifiable | Custom Properties | Additional Properties | Access Restrictions | Defined In |
|
||||||
|
| :------------------ | :--------- | :------------- | :---------------------- | :---------------- | :-------------------- | :------------------ | :---------------------------------------------------------------------------------------------- |
|
||||||
|
| Can be instantiated | No | Unknown status | Unknown identifiability | Forbidden | Allowed | none | [SignIntoFamilyRequest.schema.json\*](SignIntoFamilyRequest.schema.json "open original schema") |
|
||||||
|
|
||||||
|
## clientLevel Type
|
||||||
|
|
||||||
|
`number`
|
||||||
@@ -21,6 +21,7 @@ https://timelimit.io/SignIntoFamilyRequest
|
|||||||
| [mailAuthToken](#mailauthtoken) | `string` | Required | cannot be null | [SignIntoFamilyRequest](signintofamilyrequest-properties-mailauthtoken.md "https://timelimit.io/SignIntoFamilyRequest#/properties/mailAuthToken") |
|
| [mailAuthToken](#mailauthtoken) | `string` | Required | cannot be null | [SignIntoFamilyRequest](signintofamilyrequest-properties-mailauthtoken.md "https://timelimit.io/SignIntoFamilyRequest#/properties/mailAuthToken") |
|
||||||
| [parentDevice](#parentdevice) | `object` | Required | cannot be null | [SignIntoFamilyRequest](signintofamilyrequest-definitions-newdeviceinfo.md "https://timelimit.io/SignIntoFamilyRequest#/properties/parentDevice") |
|
| [parentDevice](#parentdevice) | `object` | Required | cannot be null | [SignIntoFamilyRequest](signintofamilyrequest-definitions-newdeviceinfo.md "https://timelimit.io/SignIntoFamilyRequest#/properties/parentDevice") |
|
||||||
| [deviceName](#devicename) | `string` | Required | cannot be null | [SignIntoFamilyRequest](signintofamilyrequest-properties-devicename.md "https://timelimit.io/SignIntoFamilyRequest#/properties/deviceName") |
|
| [deviceName](#devicename) | `string` | Required | cannot be null | [SignIntoFamilyRequest](signintofamilyrequest-properties-devicename.md "https://timelimit.io/SignIntoFamilyRequest#/properties/deviceName") |
|
||||||
|
| [clientLevel](#clientlevel) | `number` | Optional | cannot be null | [SignIntoFamilyRequest](signintofamilyrequest-properties-clientlevel.md "https://timelimit.io/SignIntoFamilyRequest#/properties/clientLevel") |
|
||||||
|
|
||||||
## mailAuthToken
|
## mailAuthToken
|
||||||
|
|
||||||
@@ -76,6 +77,24 @@ https://timelimit.io/SignIntoFamilyRequest
|
|||||||
|
|
||||||
`string`
|
`string`
|
||||||
|
|
||||||
|
## clientLevel
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
`clientLevel`
|
||||||
|
|
||||||
|
* is optional
|
||||||
|
|
||||||
|
* Type: `number`
|
||||||
|
|
||||||
|
* cannot be null
|
||||||
|
|
||||||
|
* defined in: [SignIntoFamilyRequest](signintofamilyrequest-properties-clientlevel.md "https://timelimit.io/SignIntoFamilyRequest#/properties/clientLevel")
|
||||||
|
|
||||||
|
### clientLevel Type
|
||||||
|
|
||||||
|
`number`
|
||||||
|
|
||||||
# SignIntoFamilyRequest Definitions
|
# SignIntoFamilyRequest Definitions
|
||||||
|
|
||||||
## Definitions group NewDeviceInfo
|
## Definitions group NewDeviceInfo
|
||||||
|
|||||||
@@ -57,3 +57,5 @@
|
|||||||
- SIGN_SECRET
|
- SIGN_SECRET
|
||||||
- used for signing tokens
|
- used for signing tokens
|
||||||
- if not set or set to an empty string, then the features that depend on it are disabled
|
- if not set or set to an empty string, then the features that depend on it are disabled
|
||||||
|
- UA_MAIL_BLOCKLIST
|
||||||
|
- List of user agents, separated by comma, that are not allowed to trigger sign in mails
|
||||||
|
|||||||
@@ -0,0 +1,192 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:o="urn:schemas-microsoft-com:office:office">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<title>
|
||||||
|
</title>
|
||||||
|
<!--[if !mso]><!-->
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
|
<!--<![endif]-->
|
||||||
|
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<style type="text/css">
|
||||||
|
#outlook a {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
-webkit-text-size-adjust: 100%;
|
||||||
|
-ms-text-size-adjust: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
table,
|
||||||
|
td {
|
||||||
|
border-collapse: collapse;
|
||||||
|
mso-table-lspace: 0pt;
|
||||||
|
mso-table-rspace: 0pt;
|
||||||
|
}
|
||||||
|
|
||||||
|
img {
|
||||||
|
border: 0;
|
||||||
|
height: auto;
|
||||||
|
line-height: 100%;
|
||||||
|
outline: none;
|
||||||
|
text-decoration: none;
|
||||||
|
-ms-interpolation-mode: bicubic;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
display: block;
|
||||||
|
margin: 13px 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<!--[if mso]>
|
||||||
|
<noscript>
|
||||||
|
<xml>
|
||||||
|
<o:OfficeDocumentSettings>
|
||||||
|
<o:AllowPNG/>
|
||||||
|
<o:PixelsPerInch>96</o:PixelsPerInch>
|
||||||
|
</o:OfficeDocumentSettings>
|
||||||
|
</xml>
|
||||||
|
</noscript>
|
||||||
|
<![endif]-->
|
||||||
|
<!--[if lte mso 11]>
|
||||||
|
<style type="text/css">
|
||||||
|
.mj-outlook-group-fix { width:100% !important; }
|
||||||
|
</style>
|
||||||
|
<![endif]-->
|
||||||
|
<!--[if !mso]><!-->
|
||||||
|
<!--<![endif]-->
|
||||||
|
<style type="text/css">
|
||||||
|
@media only screen and (min-width:480px) {
|
||||||
|
.mj-column-per-100 {
|
||||||
|
width: 100% !important;
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<style media="screen and (min-width:480px)">
|
||||||
|
.moz-text-html .mj-column-per-100 {
|
||||||
|
width: 100% !important;
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<style type="text/css">
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body style="word-spacing:normal;">
|
||||||
|
<div style="">
|
||||||
|
<!--[if mso | IE]><table align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:600px;" width="600" bgcolor="#009688" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
|
||||||
|
<div style="background:#009688;background-color:#009688;margin:0px auto;max-width:600px;">
|
||||||
|
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="background:#009688;background-color:#009688;width:100%;">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td style="direction:ltr;font-size:0px;padding:20px 0;text-align:center;">
|
||||||
|
<!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" style="vertical-align:top;width:600px;" ><![endif]-->
|
||||||
|
<div class="mj-column-per-100 mj-outlook-group-fix" style="font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
|
||||||
|
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td align="left" style="font-size:0px;padding:10px 25px;word-break:break-word;">
|
||||||
|
<div style="font-family:Ubuntu, Helvetica, Arial, sans-serif;font-size:20px;line-height:1;text-align:left;color:#ffffff;">TimeLimit</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<!--[if mso | IE]></td></tr></table><![endif]-->
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<!--[if mso | IE]></td></tr></table><table align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:600px;" width="600" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
|
||||||
|
<div style="margin:0px auto;max-width:600px;">
|
||||||
|
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width:100%;">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td style="direction:ltr;font-size:0px;padding:20px 0;text-align:center;">
|
||||||
|
<!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" style="vertical-align:top;width:600px;" ><![endif]-->
|
||||||
|
<div class="mj-column-per-100 mj-outlook-group-fix" style="font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
|
||||||
|
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td align="left" style="font-size:0px;padding:10px 25px;word-break:break-word;">
|
||||||
|
<div style="font-family:Ubuntu, Helvetica, Arial, sans-serif;font-size:13px;line-height:1;text-align:left;color:#000000;">
|
||||||
|
<p> Sie haben eine Löschung Ihres Benutzerkontos angefordert. Diese wurde soeben durchgeführt. </p>
|
||||||
|
<p> You requested the deletion of your account. This deletion is finished now. </p>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<!--[if mso | IE]></td></tr></table><![endif]-->
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<!--[if mso | IE]></td></tr></table><table align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:600px;" width="600" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
|
||||||
|
<div style="margin:0px auto;max-width:600px;">
|
||||||
|
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width:100%;">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td style="direction:ltr;font-size:0px;padding:20px 0;text-align:center;">
|
||||||
|
<!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" style="vertical-align:top;width:600px;" ><![endif]-->
|
||||||
|
<div class="mj-column-per-100 mj-outlook-group-fix" style="font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
|
||||||
|
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td align="center" style="font-size:0px;padding:10px 25px;word-break:break-word;">
|
||||||
|
<p style="border-top:dashed 1px lightgrey;font-size:1px;margin:0px auto;width:100%;">
|
||||||
|
</p>
|
||||||
|
<!--[if mso | IE]><table align="center" border="0" cellpadding="0" cellspacing="0" style="border-top:dashed 1px lightgrey;font-size:1px;margin:0px auto;width:550px;" role="presentation" width="550px" ><tr><td style="height:0;line-height:0;">
|
||||||
|
</td></tr></table><![endif]-->
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<!--[if mso | IE]></td></tr></table><![endif]-->
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<!--[if mso | IE]></td></tr></table><table align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:600px;" width="600" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
|
||||||
|
<div style="margin:0px auto;max-width:600px;">
|
||||||
|
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width:100%;">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td style="direction:ltr;font-size:0px;padding:20px 0;text-align:center;">
|
||||||
|
<!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" style="vertical-align:top;width:600px;" ><![endif]-->
|
||||||
|
<div class="mj-column-per-100 mj-outlook-group-fix" style="font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
|
||||||
|
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td align="left" style="font-size:0px;padding:10px 25px;word-break:break-word;">
|
||||||
|
<div style="font-family:Ubuntu, Helvetica, Arial, sans-serif;font-size:13px;line-height:1;text-align:left;color:#000000;">
|
||||||
|
<p> Sie erhalten diese Nachricht aufgrund Ihrer Anfrage. Falls Sie Fragen haben können Sie einfach auf diese E-Mail antworten. </p>
|
||||||
|
<p> You got this mail due to your request. If you have got any questions, then you can reply to this message. </p>
|
||||||
|
<p> © <%= mailimprint %> </p>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<!--[if mso | IE]></td></tr></table><![endif]-->
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<!--[if mso | IE]></td></tr></table><![endif]-->
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
<mjml>
|
||||||
|
<mj-body>
|
||||||
|
<mj-section background-color="#009688">
|
||||||
|
<mj-column>
|
||||||
|
<mj-text font-size="20px" color="#ffffff">TimeLimit</mj-text>
|
||||||
|
</mj-column>
|
||||||
|
</mj-section>
|
||||||
|
<mj-section>
|
||||||
|
<mj-column>
|
||||||
|
<mj-text>
|
||||||
|
<p>
|
||||||
|
Sie haben eine Löschung Ihres Benutzerkontos angefordert. Diese wurde soeben durchgeführt.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
You requested the deletion of your account. This deletion is finished now.
|
||||||
|
</p>
|
||||||
|
</mj-text>
|
||||||
|
</mj-column>
|
||||||
|
</mj-section>
|
||||||
|
<mj-section>
|
||||||
|
<mj-column>
|
||||||
|
<mj-divider border-width="1px" border-style="dashed" border-color="lightgrey" />
|
||||||
|
</mj-column>
|
||||||
|
</mj-section>
|
||||||
|
<mj-section>
|
||||||
|
<mj-column>
|
||||||
|
<mj-text>
|
||||||
|
<p>
|
||||||
|
Sie erhalten diese Nachricht aufgrund Ihrer Anfrage.
|
||||||
|
Falls Sie Fragen haben können Sie einfach auf diese E-Mail antworten.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
You got this mail due to your request.
|
||||||
|
If you have got any questions, then you can reply to this message.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
© <%= mailimprint %>
|
||||||
|
</p>
|
||||||
|
</mj-text>
|
||||||
|
</mj-column>
|
||||||
|
</mj-section>
|
||||||
|
</mj-body>
|
||||||
|
</mjml>
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
Konto gelöscht/Account deleted
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
Sie haben eine Löschung Ihres Benutzerkontos angefordert. Diese wurde soeben durchgeführt.
|
||||||
|
|
||||||
|
You requested the deletion of your account. This deletion is finished now.
|
||||||
|
|
||||||
|
----------------------
|
||||||
|
|
||||||
|
Sie erhalten diese Nachricht aufgrund Ihrer Anfrage.
|
||||||
|
Falls Sie Fragen haben können Sie einfach auf diese E-Mail antworten.
|
||||||
|
|
||||||
|
You got this mail due to your request.
|
||||||
|
If you have got any questions, then you can reply to this message.
|
||||||
|
|
||||||
|
<C> <%- mailimprint %>
|
||||||
Generated
+3662
-2263
File diff suppressed because it is too large
Load Diff
+7
-8
@@ -38,12 +38,11 @@
|
|||||||
"@types/lodash": "^4.14.166",
|
"@types/lodash": "^4.14.166",
|
||||||
"@types/node": "^16.11.59",
|
"@types/node": "^16.11.59",
|
||||||
"@types/nodemailer": "^6.4.4",
|
"@types/nodemailer": "^6.4.4",
|
||||||
"@types/umzug": "^2.3.0",
|
"@typescript-eslint/eslint-plugin": "^7.18.0",
|
||||||
"@typescript-eslint/eslint-plugin": "^5.10.0",
|
"@typescript-eslint/parser": "^7.18.0",
|
||||||
"@typescript-eslint/parser": "^5.10.0",
|
|
||||||
"eslint": "^8.7.0",
|
"eslint": "^8.7.0",
|
||||||
"rimraf": "^3.0.2",
|
"rimraf": "^3.0.2",
|
||||||
"typescript": "^4.4.4",
|
"typescript": "^5.5.4",
|
||||||
"typescript-json-schema": "^0.52.0"
|
"typescript-json-schema": "^0.52.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -57,16 +56,16 @@
|
|||||||
"jose": "^4.9.3",
|
"jose": "^4.9.3",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"mariadb": "^2.5.2",
|
"mariadb": "^2.5.2",
|
||||||
"nodemailer": "^6.7.2",
|
"nodemailer": "^7.0.9",
|
||||||
"pg": "^8.5.1",
|
"pg": "^8.5.1",
|
||||||
"pg-hstore": "^2.3.3",
|
"pg-hstore": "^2.3.3",
|
||||||
|
"pkijs": "^3.0.16",
|
||||||
"rate-limiter-flexible": "^2.1.15",
|
"rate-limiter-flexible": "^2.1.15",
|
||||||
"sequelize": "^6.25.5",
|
"sequelize": "^6.25.5",
|
||||||
"socket.io": "^4.0.1",
|
"socket.io": "^4.0.1",
|
||||||
"sqlite3": "^4.0.0",
|
"umzug": "^3.8.1"
|
||||||
"umzug": "^2.3.0"
|
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"sqlite3": "^4.0.0"
|
"sqlite3": "^5.0.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* server component for the TimeLimit App
|
* server component for the TimeLimit App
|
||||||
* Copyright (C) 2019 - 2022 Jonas Lochmann
|
* Copyright (C) 2019 - 2024 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
|
||||||
@@ -43,7 +43,8 @@ const types = [
|
|||||||
'RequestWithAuthToken',
|
'RequestWithAuthToken',
|
||||||
'SendMailLoginCodeRequest',
|
'SendMailLoginCodeRequest',
|
||||||
'SignInByMailCodeRequest',
|
'SignInByMailCodeRequest',
|
||||||
'IdentityTokenPayload'
|
'IdentityTokenPayload',
|
||||||
|
'DeleteAccountPayload',
|
||||||
]
|
]
|
||||||
|
|
||||||
const docOnlyTypes = [
|
const docOnlyTypes = [
|
||||||
@@ -57,7 +58,9 @@ const allTypes = [
|
|||||||
|
|
||||||
const settings = {
|
const settings = {
|
||||||
required: true,
|
required: true,
|
||||||
noExtraProps: true
|
noExtraProps: true,
|
||||||
|
// otherwise it finds errors in dependencies that we don't care about
|
||||||
|
ignoreErrors: true
|
||||||
};
|
};
|
||||||
|
|
||||||
const compilerOptions = {
|
const compilerOptions = {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* server component for the TimeLimit App
|
* server component for the TimeLimit App
|
||||||
* Copyright (C) 2019 - 2022 Jonas Lochmann
|
* Copyright (C) 2019 - 2023 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,6 +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 { minPlatformLevel, maxPlatformLevel, minPlatformTypeLength, maxPlatformTypeLength } from '../database/device'
|
||||||
import { NewPermissionStatus } from '../model/newpermissionstatus'
|
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'
|
||||||
@@ -33,6 +34,8 @@ export class UpdateDeviceStatusAction extends AppLogicAction {
|
|||||||
readonly didReboot: boolean
|
readonly didReboot: boolean
|
||||||
readonly isQOrLaterNow: boolean
|
readonly isQOrLaterNow: boolean
|
||||||
readonly addedManipulationFlags: number
|
readonly addedManipulationFlags: number
|
||||||
|
readonly platformType?: string
|
||||||
|
readonly platformLevel?: number
|
||||||
|
|
||||||
constructor ({
|
constructor ({
|
||||||
newProtetionLevel,
|
newProtetionLevel,
|
||||||
@@ -43,7 +46,9 @@ export class UpdateDeviceStatusAction extends AppLogicAction {
|
|||||||
newAppVersion,
|
newAppVersion,
|
||||||
didReboot,
|
didReboot,
|
||||||
isQOrLaterNow,
|
isQOrLaterNow,
|
||||||
addedManipulationFlags
|
addedManipulationFlags,
|
||||||
|
platformType,
|
||||||
|
platformLevel
|
||||||
}: {
|
}: {
|
||||||
newProtetionLevel?: ProtectionLevel
|
newProtetionLevel?: ProtectionLevel
|
||||||
newUsageStatsPermissionStatus?: RuntimePermissionStatus
|
newUsageStatsPermissionStatus?: RuntimePermissionStatus
|
||||||
@@ -54,6 +59,8 @@ export class UpdateDeviceStatusAction extends AppLogicAction {
|
|||||||
didReboot: boolean
|
didReboot: boolean
|
||||||
isQOrLaterNow: boolean
|
isQOrLaterNow: boolean
|
||||||
addedManipulationFlags: number
|
addedManipulationFlags: number
|
||||||
|
platformType?: string
|
||||||
|
platformLevel?: number
|
||||||
}) {
|
}) {
|
||||||
super()
|
super()
|
||||||
|
|
||||||
@@ -67,6 +74,20 @@ export class UpdateDeviceStatusAction extends AppLogicAction {
|
|||||||
|
|
||||||
assertSafeInteger({ actionType, field: 'addedManipulationFlags', value: addedManipulationFlags })
|
assertSafeInteger({ actionType, field: 'addedManipulationFlags', value: addedManipulationFlags })
|
||||||
|
|
||||||
|
if (platformType !== undefined) {
|
||||||
|
if (platformType.length < minPlatformTypeLength || platformType.length > maxPlatformTypeLength) {
|
||||||
|
throwOutOfRange({ actionType, field: 'platformType.length', value: platformType.length })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (platformLevel !== undefined) {
|
||||||
|
assertSafeInteger({ actionType, field: 'platformLevel', value: platformLevel })
|
||||||
|
|
||||||
|
if (platformLevel < minPlatformLevel || platformLevel > maxPlatformLevel) {
|
||||||
|
throwOutOfRange({ actionType, field: 'platformLevel', value: platformLevel })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
this.newProtetionLevel = newProtetionLevel
|
this.newProtetionLevel = newProtetionLevel
|
||||||
this.newUsageStatsPermissionStatus = newUsageStatsPermissionStatus
|
this.newUsageStatsPermissionStatus = newUsageStatsPermissionStatus
|
||||||
this.newNotificationAccessPermission = newNotificationAccessPermission
|
this.newNotificationAccessPermission = newNotificationAccessPermission
|
||||||
@@ -76,6 +97,8 @@ export class UpdateDeviceStatusAction extends AppLogicAction {
|
|||||||
this.didReboot = didReboot
|
this.didReboot = didReboot
|
||||||
this.isQOrLaterNow = isQOrLaterNow
|
this.isQOrLaterNow = isQOrLaterNow
|
||||||
this.addedManipulationFlags = addedManipulationFlags
|
this.addedManipulationFlags = addedManipulationFlags
|
||||||
|
this.platformType = platformType
|
||||||
|
this.platformLevel = platformLevel
|
||||||
}
|
}
|
||||||
|
|
||||||
static parse = ({
|
static parse = ({
|
||||||
@@ -87,7 +110,9 @@ export class UpdateDeviceStatusAction extends AppLogicAction {
|
|||||||
appVersion,
|
appVersion,
|
||||||
didReboot,
|
didReboot,
|
||||||
isQOrLaterNow,
|
isQOrLaterNow,
|
||||||
addedManipulationFlags
|
addedManipulationFlags,
|
||||||
|
platformType,
|
||||||
|
platformLevel
|
||||||
}: SerializedUpdateDeviceStatusAction) => (
|
}: SerializedUpdateDeviceStatusAction) => (
|
||||||
new UpdateDeviceStatusAction({
|
new UpdateDeviceStatusAction({
|
||||||
newProtetionLevel: protectionLevel,
|
newProtetionLevel: protectionLevel,
|
||||||
@@ -98,7 +123,9 @@ export class UpdateDeviceStatusAction extends AppLogicAction {
|
|||||||
newAppVersion: appVersion,
|
newAppVersion: appVersion,
|
||||||
didReboot: !!didReboot,
|
didReboot: !!didReboot,
|
||||||
isQOrLaterNow: !!isQOrLaterNow,
|
isQOrLaterNow: !!isQOrLaterNow,
|
||||||
addedManipulationFlags: addedManipulationFlags || 0
|
addedManipulationFlags: addedManipulationFlags || 0,
|
||||||
|
platformType: platformType,
|
||||||
|
platformLevel: platformLevel
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -114,4 +141,6 @@ export interface SerializedUpdateDeviceStatusAction {
|
|||||||
didReboot?: boolean
|
didReboot?: boolean
|
||||||
isQOrLaterNow?: boolean
|
isQOrLaterNow?: boolean
|
||||||
addedManipulationFlags?: number
|
addedManipulationFlags?: number
|
||||||
|
platformType?: string
|
||||||
|
platformLevel?: number
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* server component for the TimeLimit App
|
* server component for the TimeLimit App
|
||||||
* Copyright (C) 2019 - 2020 Jonas Lochmann
|
* Copyright (C) 2019 - 2024 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
|
||||||
@@ -32,11 +32,12 @@ export class UpdateTimelimitRuleAction extends ParentAction {
|
|||||||
readonly sessionDurationMilliseconds: number
|
readonly sessionDurationMilliseconds: number
|
||||||
readonly sessionPauseMilliseconds: number
|
readonly sessionPauseMilliseconds: number
|
||||||
readonly perDay: boolean
|
readonly perDay: boolean
|
||||||
|
readonly expiresAt?: number
|
||||||
|
|
||||||
constructor ({
|
constructor ({
|
||||||
ruleId, maximumTimeInMillis, dayMask, applyToExtraTimeUsage,
|
ruleId, maximumTimeInMillis, dayMask, applyToExtraTimeUsage,
|
||||||
start, end, sessionDurationMilliseconds, sessionPauseMilliseconds,
|
start, end, sessionDurationMilliseconds, sessionPauseMilliseconds,
|
||||||
perDay
|
perDay, expiresAt
|
||||||
}: {
|
}: {
|
||||||
ruleId: string
|
ruleId: string
|
||||||
maximumTimeInMillis: number
|
maximumTimeInMillis: number
|
||||||
@@ -47,6 +48,7 @@ export class UpdateTimelimitRuleAction extends ParentAction {
|
|||||||
sessionDurationMilliseconds: number
|
sessionDurationMilliseconds: number
|
||||||
sessionPauseMilliseconds: number
|
sessionPauseMilliseconds: number
|
||||||
perDay: boolean
|
perDay: boolean
|
||||||
|
expiresAt?: number
|
||||||
}) {
|
}) {
|
||||||
super()
|
super()
|
||||||
|
|
||||||
@@ -59,6 +61,7 @@ export class UpdateTimelimitRuleAction extends ParentAction {
|
|||||||
this.sessionDurationMilliseconds = sessionDurationMilliseconds
|
this.sessionDurationMilliseconds = sessionDurationMilliseconds
|
||||||
this.sessionPauseMilliseconds = sessionPauseMilliseconds
|
this.sessionPauseMilliseconds = sessionPauseMilliseconds
|
||||||
this.perDay = perDay
|
this.perDay = perDay
|
||||||
|
this.expiresAt = expiresAt
|
||||||
|
|
||||||
assertIdWithinFamily({ actionType, field: 'ruleId', value: ruleId })
|
assertIdWithinFamily({ actionType, field: 'ruleId', value: ruleId })
|
||||||
|
|
||||||
@@ -92,9 +95,17 @@ export class UpdateTimelimitRuleAction extends ParentAction {
|
|||||||
staticMessage: 'session duration lesser than zero'
|
staticMessage: 'session duration lesser than zero'
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (expiresAt !== undefined) {
|
||||||
|
assertSafeInteger({ actionType, field: 'expiresAt', value: expiresAt })
|
||||||
|
|
||||||
|
if (expiresAt <= 0) {
|
||||||
|
throwOutOfRange({ actionType, field: 'expiresAt', value: expiresAt })
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static parse = ({ ruleId, time, days, extraTime, start, end, dur, pause, perDay }: SerializedUpdateTimelimitRuleAction) => (
|
static parse = ({ ruleId, time, days, extraTime, start, end, dur, pause, perDay, e }: SerializedUpdateTimelimitRuleAction) => (
|
||||||
new UpdateTimelimitRuleAction({
|
new UpdateTimelimitRuleAction({
|
||||||
ruleId,
|
ruleId,
|
||||||
maximumTimeInMillis: time,
|
maximumTimeInMillis: time,
|
||||||
@@ -104,7 +115,8 @@ export class UpdateTimelimitRuleAction extends ParentAction {
|
|||||||
end: end ?? MinuteOfDay.MAX,
|
end: end ?? MinuteOfDay.MAX,
|
||||||
sessionDurationMilliseconds: dur ?? 0,
|
sessionDurationMilliseconds: dur ?? 0,
|
||||||
sessionPauseMilliseconds: pause ?? 0,
|
sessionPauseMilliseconds: pause ?? 0,
|
||||||
perDay: perDay ?? false
|
perDay: perDay ?? false,
|
||||||
|
expiresAt: e
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -120,4 +132,5 @@ export interface SerializedUpdateTimelimitRuleAction {
|
|||||||
dur?: number
|
dur?: number
|
||||||
pause?: number
|
pause?: number
|
||||||
perDay?: boolean
|
perDay?: boolean
|
||||||
|
e?: number
|
||||||
}
|
}
|
||||||
|
|||||||
+13
-3
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* server component for the TimeLimit App
|
* server component for the TimeLimit App
|
||||||
* Copyright (C) 2019 - 2021 Jonas Lochmann
|
* Copyright (C) 2019 - 2024 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,7 +17,8 @@
|
|||||||
|
|
||||||
import { json } from 'body-parser'
|
import { json } from 'body-parser'
|
||||||
import { Router } from 'express'
|
import { Router } from 'express'
|
||||||
import { BadRequest } from 'http-errors'
|
import { BadRequest, Forbidden } from 'http-errors'
|
||||||
|
import { config } from '../config'
|
||||||
import { Database } from '../database'
|
import { Database } from '../database'
|
||||||
import { sendLoginCode, signInByMailCode } from '../function/authentication/login-by-mail'
|
import { sendLoginCode, signInByMailCode } from '../function/authentication/login-by-mail'
|
||||||
import { isMailAddressCoveredByWhitelist, isMailServerBlacklisted, sanitizeMailAddress } from '../util/mail'
|
import { isMailAddressCoveredByWhitelist, isMailServerBlacklisted, sanitizeMailAddress } from '../util/mail'
|
||||||
@@ -25,11 +26,17 @@ import {
|
|||||||
isSendMailLoginCodeRequest,
|
isSendMailLoginCodeRequest,
|
||||||
isSignInByMailCodeRequest
|
isSignInByMailCodeRequest
|
||||||
} from './validator'
|
} from './validator'
|
||||||
|
import { analyze } from './integrity'
|
||||||
|
|
||||||
export const createAuthRouter = (database: Database) => {
|
export const createAuthRouter = (database: Database) => {
|
||||||
const router = Router()
|
const router = Router()
|
||||||
|
|
||||||
router.post('/send-mail-login-code-v2', json(), async (req, res, next) => {
|
router.post('/send-mail-login-code-v2', json(), async (req, res, next) => {
|
||||||
|
const info = {
|
||||||
|
ua: req.headers['user-agent'],
|
||||||
|
cert: analyze(req),
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (!isSendMailLoginCodeRequest(req.body)) {
|
if (!isSendMailLoginCodeRequest(req.body)) {
|
||||||
throw new BadRequest()
|
throw new BadRequest()
|
||||||
@@ -45,12 +52,15 @@ export const createAuthRouter = (database: Database) => {
|
|||||||
res.json({ mailAddressNotWhitelisted: true })
|
res.json({ mailAddressNotWhitelisted: true })
|
||||||
} else if (isMailServerBlacklisted(mail)) {
|
} else if (isMailServerBlacklisted(mail)) {
|
||||||
res.json({ mailServerBlacklisted: true })
|
res.json({ mailServerBlacklisted: true })
|
||||||
|
} else if (config.uaMailBlocklist.indexOf(req.headers['user-agent'] || '') !== -1) {
|
||||||
|
throw new Forbidden()
|
||||||
} else {
|
} else {
|
||||||
const { mailLoginToken } = await sendLoginCode({
|
const { mailLoginToken } = await sendLoginCode({
|
||||||
mail,
|
mail,
|
||||||
deviceAuthToken: req.body.deviceAuthToken,
|
deviceAuthToken: req.body.deviceAuthToken,
|
||||||
locale: req.body.locale,
|
locale: req.body.locale,
|
||||||
database
|
database,
|
||||||
|
info: Buffer.from(JSON.stringify(info), 'utf8')
|
||||||
})
|
})
|
||||||
|
|
||||||
res.json({ mailLoginToken })
|
res.json({ mailLoginToken })
|
||||||
|
|||||||
+9
-5
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* server component for the TimeLimit App
|
* server component for the TimeLimit App
|
||||||
* Copyright (C) 2019 Jonas Lochmann
|
* Copyright (C) 2019 - 2022 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
|
||||||
@@ -24,10 +24,12 @@ import { logoutAtPrimaryDevice } from '../function/child/logout-at-primary-devic
|
|||||||
import { setPrimaryDevice } from '../function/child/set-primary-device'
|
import { setPrimaryDevice } from '../function/child/set-primary-device'
|
||||||
import { WebsocketApi } from '../websocket'
|
import { WebsocketApi } from '../websocket'
|
||||||
import { isRegisterChildDeviceRequest, isRequestWithAuthToken, isUpdatePrimaryDeviceRequest } from './validator'
|
import { isRegisterChildDeviceRequest, isRequestWithAuthToken, isUpdatePrimaryDeviceRequest } from './validator'
|
||||||
|
import { EventHandler } from '../monitoring/eventhandler'
|
||||||
|
|
||||||
export const createChildRouter = ({ database, websocket }: {
|
export const createChildRouter = ({ database, websocket, eventHandler }: {
|
||||||
database: Database,
|
database: Database
|
||||||
websocket: WebsocketApi
|
websocket: WebsocketApi
|
||||||
|
eventHandler: EventHandler
|
||||||
}) => {
|
}) => {
|
||||||
const router = Router()
|
const router = Router()
|
||||||
|
|
||||||
@@ -37,15 +39,17 @@ export const createChildRouter = ({ database, websocket }: {
|
|||||||
throw new BadRequest()
|
throw new BadRequest()
|
||||||
}
|
}
|
||||||
|
|
||||||
const { deviceAuthToken, deviceId } = await addChildDevice({
|
const { deviceAuthToken, deviceId, data } = await addChildDevice({
|
||||||
request: req.body,
|
request: req.body,
|
||||||
database,
|
database,
|
||||||
|
eventHandler,
|
||||||
websocket
|
websocket
|
||||||
})
|
})
|
||||||
|
|
||||||
res.json({
|
res.json({
|
||||||
deviceAuthToken,
|
deviceAuthToken,
|
||||||
ownDeviceId: deviceId
|
ownDeviceId: deviceId,
|
||||||
|
data
|
||||||
})
|
})
|
||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
next(ex)
|
next(ex)
|
||||||
|
|||||||
+3
-3
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* server component for the TimeLimit App
|
* server component for the TimeLimit App
|
||||||
* Copyright (C) 2019 - 2020 Jonas Lochmann
|
* Copyright (C) 2019 - 2022 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,8 +47,8 @@ export const createApi = ({ database, websocket, connectedDevicesManager, eventH
|
|||||||
})
|
})
|
||||||
|
|
||||||
app.use('/auth', createAuthRouter(database))
|
app.use('/auth', createAuthRouter(database))
|
||||||
app.use('/child', createChildRouter({ database, websocket }))
|
app.use('/child', createChildRouter({ database, websocket, eventHandler }))
|
||||||
app.use('/parent', createParentRouter({ database, websocket }))
|
app.use('/parent', createParentRouter({ database, websocket, eventHandler }))
|
||||||
app.use('/purchase', createPurchaseRouter({ database, websocket }))
|
app.use('/purchase', createPurchaseRouter({ database, websocket }))
|
||||||
app.use('/sync', createSyncRouter({ database, websocket, connectedDevicesManager, eventHandler }))
|
app.use('/sync', createSyncRouter({ database, websocket, connectedDevicesManager, eventHandler }))
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,102 @@
|
|||||||
|
import { X509Certificate } from 'crypto'
|
||||||
|
import { Request } from 'express'
|
||||||
|
import { fromBER, Sequence, Integer, OctetString, Set } from 'asn1js'
|
||||||
|
import { Certificate } from 'pkijs'
|
||||||
|
|
||||||
|
export interface CertInfo {
|
||||||
|
applicationCerts: Array<string>
|
||||||
|
}
|
||||||
|
|
||||||
|
export function analyze(req: Request): CertInfo | null {
|
||||||
|
try {
|
||||||
|
const certStr = req.headers['clientcertificate']
|
||||||
|
|
||||||
|
if (typeof certStr !== 'string') return null
|
||||||
|
|
||||||
|
const nativeCert = new X509Certificate(decodeURIComponent(certStr))
|
||||||
|
|
||||||
|
const now = Date.now()
|
||||||
|
const from = Date.parse(nativeCert.validFrom)
|
||||||
|
const to = Date.parse(nativeCert.validTo)
|
||||||
|
|
||||||
|
if (from > now || to < now) return null
|
||||||
|
if (from > to || from + 1000 * 60 * 5 < to) return null
|
||||||
|
|
||||||
|
const cert1 = fromBER(nativeCert.raw)
|
||||||
|
|
||||||
|
if (cert1.offset === -1) return null
|
||||||
|
|
||||||
|
const cert2 = new Certificate({ schema: cert1.result })
|
||||||
|
const androidExtension = (cert2.extensions || []).find((item) => item.extnID === '1.3.6.1.4.1.11129.2.1.17')?.extnValue?.valueBlock?.valueHexView
|
||||||
|
|
||||||
|
if (!androidExtension) return null
|
||||||
|
|
||||||
|
const androidExtensionParsed = fromBER(androidExtension)
|
||||||
|
|
||||||
|
if (androidExtensionParsed.offset === -1) return null
|
||||||
|
|
||||||
|
const androidExtensionSequence = androidExtensionParsed.result
|
||||||
|
|
||||||
|
if (!(androidExtensionSequence instanceof Sequence)) return null
|
||||||
|
|
||||||
|
const versionInteger = androidExtensionSequence.valueBlock.value[0]
|
||||||
|
|
||||||
|
if (!(versionInteger instanceof Integer)) return null
|
||||||
|
|
||||||
|
const versionValue = versionInteger.valueBlock.valueDec
|
||||||
|
|
||||||
|
const authorizationLists = androidExtensionSequence.valueBlock.value.slice(6, 8)
|
||||||
|
|
||||||
|
let applicationId = null
|
||||||
|
|
||||||
|
for (const authList of authorizationLists) {
|
||||||
|
if (!(authList instanceof Sequence)) continue
|
||||||
|
|
||||||
|
for (const authListItem of authList.valueBlock.value) {
|
||||||
|
if (
|
||||||
|
// version 1 does not provide this data structure
|
||||||
|
versionValue !== 1 &&
|
||||||
|
authListItem.idBlock.tagNumber === 709
|
||||||
|
) {
|
||||||
|
if (!('value' in authListItem.valueBlock)) continue
|
||||||
|
|
||||||
|
const value = (authListItem.valueBlock as unknown as { value: object }).value
|
||||||
|
|
||||||
|
if (!Array.isArray(value) || value.length !== 1) continue
|
||||||
|
|
||||||
|
if (value[0] instanceof OctetString) applicationId = value[0]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!applicationId) return null
|
||||||
|
|
||||||
|
const parsedApplicationId = fromBER(applicationId.valueBlock.valueHexView)
|
||||||
|
|
||||||
|
if (parsedApplicationId.offset === -1) return null
|
||||||
|
|
||||||
|
if (!(parsedApplicationId.result instanceof Sequence)) return null
|
||||||
|
|
||||||
|
const parsedApplicationIdInfo = parsedApplicationId.result.valueBlock.value
|
||||||
|
|
||||||
|
if (parsedApplicationIdInfo.length !== 2) return null
|
||||||
|
|
||||||
|
const signatureDigests = parsedApplicationIdInfo[1]
|
||||||
|
|
||||||
|
if (!(signatureDigests instanceof Set)) return null
|
||||||
|
|
||||||
|
const applicationCerts = []
|
||||||
|
|
||||||
|
for (const cert of signatureDigests.valueBlock.value) {
|
||||||
|
if (cert instanceof OctetString) {
|
||||||
|
applicationCerts.push(Buffer.from(cert.valueBlock.valueHexView).toString('hex'))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
applicationCerts
|
||||||
|
}
|
||||||
|
} catch (ex) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
+35
-6
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* server component for the TimeLimit App
|
* server component for the TimeLimit App
|
||||||
* Copyright (C) 2019 - 2022 Jonas Lochmann
|
* Copyright (C) 2019 - 2023 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,6 +21,7 @@ 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, Transaction } from '../database'
|
import { Database, Transaction } from '../database'
|
||||||
|
import { deleteAccount } from '../function/cleanup/account-deletion'
|
||||||
import { removeDevice } from '../function/device/remove-device'
|
import { removeDevice } from '../function/device/remove-device'
|
||||||
import { createAddDeviceToken } from '../function/parent/create-add-device-token'
|
import { createAddDeviceToken } from '../function/parent/create-add-device-token'
|
||||||
import { createFamily } from '../function/parent/create-family'
|
import { createFamily } from '../function/parent/create-family'
|
||||||
@@ -31,14 +32,22 @@ import { signInIntoFamily } from '../function/parent/sign-in-into-family'
|
|||||||
import { validateU2fIntegrity, U2fValidationError } from '../function/u2f'
|
import { validateU2fIntegrity, U2fValidationError } from '../function/u2f'
|
||||||
import { createIdentityToken, MissingSignSecretException } from '../util/identity-token'
|
import { createIdentityToken, MissingSignSecretException } from '../util/identity-token'
|
||||||
import { WebsocketApi } from '../websocket'
|
import { WebsocketApi } from '../websocket'
|
||||||
|
import { EventHandler } from '../monitoring/eventhandler'
|
||||||
import {
|
import {
|
||||||
isCreateFamilyByMailTokenRequest,
|
isCreateFamilyByMailTokenRequest,
|
||||||
isCreateRegisterDeviceTokenRequest, isLinkParentMailAddressRequest,
|
isCreateRegisterDeviceTokenRequest, isLinkParentMailAddressRequest,
|
||||||
isMailAuthTokenRequestBody, isRecoverParentPasswordRequest,
|
isMailAuthTokenRequestBody, isRecoverParentPasswordRequest,
|
||||||
isRemoveDeviceRequest, isSignIntoFamilyRequest, isRequestIdentityTokenRequest
|
isRemoveDeviceRequest, isSignIntoFamilyRequest, isRequestIdentityTokenRequest,
|
||||||
|
isDeleteAccountPayload
|
||||||
} from './validator'
|
} from './validator'
|
||||||
|
|
||||||
export const createParentRouter = ({ database, websocket }: {database: Database, websocket: WebsocketApi}) => {
|
export const createParentRouter = ({
|
||||||
|
database, websocket, eventHandler
|
||||||
|
}: {
|
||||||
|
database: Database
|
||||||
|
websocket: WebsocketApi
|
||||||
|
eventHandler: EventHandler
|
||||||
|
}) => {
|
||||||
const router = Router()
|
const router = Router()
|
||||||
|
|
||||||
router.post('/get-status-by-mail-address', json(), async (req, res, next) => {
|
router.post('/get-status-by-mail-address', json(), async (req, res, next) => {
|
||||||
@@ -75,17 +84,20 @@ export const createParentRouter = ({ database, websocket }: {database: Database,
|
|||||||
|
|
||||||
const result = await createFamily({
|
const result = await createFamily({
|
||||||
database,
|
database,
|
||||||
|
eventHandler,
|
||||||
firstParentDevice: req.body.parentDevice,
|
firstParentDevice: req.body.parentDevice,
|
||||||
mailAuthToken: req.body.mailAuthToken,
|
mailAuthToken: req.body.mailAuthToken,
|
||||||
password: req.body.parentPassword,
|
password: req.body.parentPassword,
|
||||||
deviceName: req.body.deviceName,
|
deviceName: req.body.deviceName,
|
||||||
parentName: req.body.parentName,
|
parentName: req.body.parentName,
|
||||||
timeZone: req.body.timeZone
|
timeZone: req.body.timeZone,
|
||||||
|
clientLevel: req.body.clientLevel || null
|
||||||
})
|
})
|
||||||
|
|
||||||
res.json({
|
res.json({
|
||||||
deviceAuthToken: result.deviceAuthToken,
|
deviceAuthToken: result.deviceAuthToken,
|
||||||
ownDeviceId: result.deviceId
|
ownDeviceId: result.deviceId,
|
||||||
|
data: result.data
|
||||||
})
|
})
|
||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
next(ex)
|
next(ex)
|
||||||
@@ -100,15 +112,18 @@ export const createParentRouter = ({ database, websocket }: {database: Database,
|
|||||||
|
|
||||||
const result = await signInIntoFamily({
|
const result = await signInIntoFamily({
|
||||||
database,
|
database,
|
||||||
|
eventHandler,
|
||||||
newDeviceInfo: req.body.parentDevice,
|
newDeviceInfo: req.body.parentDevice,
|
||||||
mailAuthToken: req.body.mailAuthToken,
|
mailAuthToken: req.body.mailAuthToken,
|
||||||
deviceName: req.body.deviceName,
|
deviceName: req.body.deviceName,
|
||||||
|
clientLevel: req.body.clientLevel || null,
|
||||||
websocket
|
websocket
|
||||||
})
|
})
|
||||||
|
|
||||||
res.json({
|
res.json({
|
||||||
deviceAuthToken: result.deviceAuthToken,
|
deviceAuthToken: result.deviceAuthToken,
|
||||||
ownDeviceId: result.deviceId
|
ownDeviceId: result.deviceId,
|
||||||
|
data: result.data
|
||||||
})
|
})
|
||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
next(ex)
|
next(ex)
|
||||||
@@ -343,5 +358,19 @@ export const createParentRouter = ({ database, websocket }: {database: Database,
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
router.post('/delete-account', json(), async (req, res, next) => {
|
||||||
|
try {
|
||||||
|
if (!isDeleteAccountPayload(req.body)) {
|
||||||
|
throw new BadRequest()
|
||||||
|
}
|
||||||
|
|
||||||
|
await deleteAccount({ database, request: req.body, websocket })
|
||||||
|
|
||||||
|
res.sendStatus(200)
|
||||||
|
} catch (ex) {
|
||||||
|
next(ex)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
return router
|
return router
|
||||||
}
|
}
|
||||||
|
|||||||
+9
-1
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* server component for the TimeLimit App
|
* server component for the TimeLimit App
|
||||||
* Copyright (C) 2019 - 2022 Jonas Lochmann
|
* Copyright (C) 2019 - 2023 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
|
||||||
@@ -84,12 +84,14 @@ export interface CreateFamilyByMailTokenRequest {
|
|||||||
deviceName: string
|
deviceName: string
|
||||||
timeZone: string
|
timeZone: string
|
||||||
parentName: string
|
parentName: string
|
||||||
|
clientLevel?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SignIntoFamilyRequest {
|
export interface SignIntoFamilyRequest {
|
||||||
mailAuthToken: string
|
mailAuthToken: string
|
||||||
parentDevice: NewDeviceInfo
|
parentDevice: NewDeviceInfo
|
||||||
deviceName: string
|
deviceName: string
|
||||||
|
clientLevel?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface RecoverParentPasswordRequest {
|
export interface RecoverParentPasswordRequest {
|
||||||
@@ -101,6 +103,7 @@ export interface RegisterChildDeviceRequest {
|
|||||||
registerToken: string
|
registerToken: string
|
||||||
childDevice: NewDeviceInfo
|
childDevice: NewDeviceInfo
|
||||||
deviceName: string
|
deviceName: string
|
||||||
|
clientLevel?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CreateRegisterDeviceTokenRequest {
|
export interface CreateRegisterDeviceTokenRequest {
|
||||||
@@ -173,5 +176,10 @@ export type IdentityTokenPayload = IdentityTokenCreatePayload & {
|
|||||||
exp: number
|
exp: number
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface DeleteAccountPayload {
|
||||||
|
deviceAuthToken: string
|
||||||
|
mailAuthTokens: Array<string>
|
||||||
|
}
|
||||||
|
|
||||||
export { SerializedParentAction, SerializedChildAction, SerializedAppLogicAction } from '../action/serialization'
|
export { SerializedParentAction, SerializedChildAction, SerializedAppLogicAction } from '../action/serialization'
|
||||||
export { ServerDataStatus } from '../object/serverdatastatus'
|
export { ServerDataStatus } from '../object/serverdatastatus'
|
||||||
|
|||||||
+50
-1
@@ -1,5 +1,5 @@
|
|||||||
// tslint:disable
|
// tslint:disable
|
||||||
import { ClientPushChangesRequest, ClientPullChangesRequest, MailAuthTokenRequestBody, CreateFamilyByMailTokenRequest, SignIntoFamilyRequest, RecoverParentPasswordRequest, RegisterChildDeviceRequest, SerializedParentAction, SerializedAppLogicAction, SerializedChildAction, CreateRegisterDeviceTokenRequest, CanDoPurchaseRequest, FinishPurchaseByGooglePlayRequest, LinkParentMailAddressRequest, UpdatePrimaryDeviceRequest, RemoveDeviceRequest, RequestIdentityTokenRequest, RequestWithAuthToken, SendMailLoginCodeRequest, SignInByMailCodeRequest, IdentityTokenPayload } from './schema'
|
import { ClientPushChangesRequest, ClientPullChangesRequest, MailAuthTokenRequestBody, CreateFamilyByMailTokenRequest, SignIntoFamilyRequest, RecoverParentPasswordRequest, RegisterChildDeviceRequest, SerializedParentAction, SerializedAppLogicAction, SerializedChildAction, CreateRegisterDeviceTokenRequest, CanDoPurchaseRequest, FinishPurchaseByGooglePlayRequest, LinkParentMailAddressRequest, UpdatePrimaryDeviceRequest, RemoveDeviceRequest, RequestIdentityTokenRequest, RequestWithAuthToken, SendMailLoginCodeRequest, SignInByMailCodeRequest, IdentityTokenPayload, DeleteAccountPayload } from './schema'
|
||||||
import Ajv from 'ajv'
|
import Ajv from 'ajv'
|
||||||
const ajv = new Ajv()
|
const ajv = new Ajv()
|
||||||
|
|
||||||
@@ -409,6 +409,9 @@ const definitions = {
|
|||||||
},
|
},
|
||||||
"perDay": {
|
"perDay": {
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"e": {
|
||||||
|
"type": "number"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"additionalProperties": false,
|
"additionalProperties": false,
|
||||||
@@ -1417,6 +1420,9 @@ const definitions = {
|
|||||||
},
|
},
|
||||||
"perDay": {
|
"perDay": {
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"e": {
|
||||||
|
"type": "number"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"additionalProperties": false,
|
"additionalProperties": false,
|
||||||
@@ -1982,6 +1988,12 @@ const definitions = {
|
|||||||
},
|
},
|
||||||
"addedManipulationFlags": {
|
"addedManipulationFlags": {
|
||||||
"type": "number"
|
"type": "number"
|
||||||
|
},
|
||||||
|
"platformType": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"platformLevel": {
|
||||||
|
"type": "number"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"additionalProperties": false,
|
"additionalProperties": false,
|
||||||
@@ -2164,6 +2176,12 @@ const definitions = {
|
|||||||
},
|
},
|
||||||
"pk": {
|
"pk": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
|
},
|
||||||
|
"pType": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"pLevel": {
|
||||||
|
"type": "number"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"additionalProperties": false,
|
"additionalProperties": false,
|
||||||
@@ -2192,6 +2210,7 @@ const definitions = {
|
|||||||
"model",
|
"model",
|
||||||
"name",
|
"name",
|
||||||
"networkTime",
|
"networkTime",
|
||||||
|
"pLevel",
|
||||||
"qOrLater",
|
"qOrLater",
|
||||||
"reboot",
|
"reboot",
|
||||||
"rebootIsManipulation",
|
"rebootIsManipulation",
|
||||||
@@ -2930,6 +2949,9 @@ export const isCreateFamilyByMailTokenRequest: (value: unknown) => value is Crea
|
|||||||
},
|
},
|
||||||
"parentName": {
|
"parentName": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
|
},
|
||||||
|
"clientLevel": {
|
||||||
|
"type": "number"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"additionalProperties": false,
|
"additionalProperties": false,
|
||||||
@@ -2955,6 +2977,9 @@ export const isSignIntoFamilyRequest: (value: unknown) => value is SignIntoFamil
|
|||||||
},
|
},
|
||||||
"deviceName": {
|
"deviceName": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
|
},
|
||||||
|
"clientLevel": {
|
||||||
|
"type": "number"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"additionalProperties": false,
|
"additionalProperties": false,
|
||||||
@@ -2995,6 +3020,9 @@ export const isRegisterChildDeviceRequest: (value: unknown) => value is Register
|
|||||||
},
|
},
|
||||||
"deviceName": {
|
"deviceName": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
|
},
|
||||||
|
"clientLevel": {
|
||||||
|
"type": "number"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"additionalProperties": false,
|
"additionalProperties": false,
|
||||||
@@ -3484,3 +3512,24 @@ export const isIdentityTokenPayload: (value: unknown) => value is IdentityTokenP
|
|||||||
"definitions": definitions,
|
"definitions": definitions,
|
||||||
"$schema": "http://json-schema.org/draft-07/schema#"
|
"$schema": "http://json-schema.org/draft-07/schema#"
|
||||||
})
|
})
|
||||||
|
export const isDeleteAccountPayload: (value: unknown) => value is DeleteAccountPayload = ajv.compile({
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"deviceAuthToken": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"mailAuthTokens": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"additionalProperties": false,
|
||||||
|
"required": [
|
||||||
|
"deviceAuthToken",
|
||||||
|
"mailAuthTokens"
|
||||||
|
],
|
||||||
|
"definitions": definitions,
|
||||||
|
"$schema": "http://json-schema.org/draft-07/schema#"
|
||||||
|
})
|
||||||
|
|||||||
+13
-7
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* server component for the TimeLimit App
|
* server component for the TimeLimit App
|
||||||
* Copyright (C) 2019 - 2022 Jonas Lochmann
|
* Copyright (C) 2019 - 2024 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 @@ interface Config {
|
|||||||
pingInterval: number
|
pingInterval: number
|
||||||
alwaysPro: boolean
|
alwaysPro: boolean
|
||||||
signSecret: string
|
signSecret: string
|
||||||
|
uaMailBlocklist: Array<string>
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseYesNo (value: string) {
|
function parseYesNo (value: string) {
|
||||||
@@ -34,12 +35,17 @@ function parseYesNo (value: string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const config: Config = {
|
function parseList(list: string) {
|
||||||
mailWhitelist: (process.env.MAIL_WHITELIST || '').split(',').map((item) => item.trim()).filter((item) => item.length > 0),
|
return list.split(',').map((item) => item.trim()).filter((item) => item.length > 0)
|
||||||
disableSignup: parseYesNo(process.env.DISABLE_SIGNUP || 'no'),
|
|
||||||
pingInterval: parseInt(process.env.PING_INTERVAL_SEC || '25', 10) * 1000,
|
|
||||||
alwaysPro: process.env.ALWAYS_PRO ? parseYesNo(process.env.ALWAYS_PRO) : false,
|
|
||||||
signSecret: process.env.SIGN_SECRET || ''
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class ParseYesNoException extends Error {}
|
class ParseYesNoException extends Error {}
|
||||||
|
|
||||||
|
export const config: Config = {
|
||||||
|
mailWhitelist: parseList(process.env.MAIL_WHITELIST || ''),
|
||||||
|
disableSignup: parseYesNo(process.env.DISABLE_SIGNUP || 'no'),
|
||||||
|
pingInterval: parseInt(process.env.PING_INTERVAL_SEC || '25', 10) * 1000,
|
||||||
|
alwaysPro: process.env.ALWAYS_PRO ? parseYesNo(process.env.ALWAYS_PRO) : false,
|
||||||
|
signSecret: process.env.SIGN_SECRET || '',
|
||||||
|
uaMailBlocklist: parseList(process.env.UA_MAIL_BLOCKLIST || '')
|
||||||
|
}
|
||||||
|
|||||||
+36
-3
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* server component for the TimeLimit App
|
* server component for the TimeLimit App
|
||||||
* Copyright (C) 2019 - 2022 Jonas Lochmann
|
* Copyright (C) 2019 - 2023 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
|
||||||
@@ -37,6 +37,12 @@ export const DeviceManipulationFlags = {
|
|||||||
ALL: 1
|
ALL: 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const minPlatformTypeLength = 1
|
||||||
|
export const maxPlatformTypeLength = 8
|
||||||
|
|
||||||
|
export const minPlatformLevel = 0
|
||||||
|
export const maxPlatformLevel = 128
|
||||||
|
|
||||||
export interface DeviceAttributesVersion1 {
|
export interface DeviceAttributesVersion1 {
|
||||||
familyId: string
|
familyId: string
|
||||||
deviceId: string
|
deviceId: string
|
||||||
@@ -119,11 +125,17 @@ export interface DeviceAttributesVersion14 {
|
|||||||
nextKeyReplySequenceNumber: string
|
nextKeyReplySequenceNumber: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface DeviceAttributesVersion15 {
|
||||||
|
platformType: string | null
|
||||||
|
platformLevel: number
|
||||||
|
}
|
||||||
|
|
||||||
export type DeviceAttributes = DeviceAttributesVersion1 & DeviceAttributesVersion2 &
|
export type DeviceAttributes = DeviceAttributesVersion1 & DeviceAttributesVersion2 &
|
||||||
DeviceAttributesVersion3 & DeviceAttributesVersion4 & DeviceAttributesVersion5 &
|
DeviceAttributesVersion3 & DeviceAttributesVersion4 & DeviceAttributesVersion5 &
|
||||||
DeviceAttributesVersion6 & DeviceAttributesVersion7 & DeviceAttributesVersion8 &
|
DeviceAttributesVersion6 & DeviceAttributesVersion7 & DeviceAttributesVersion8 &
|
||||||
DeviceAttributesVersion9 & DeviceAttributesVersion10 & DeviceAttributesVersion11 &
|
DeviceAttributesVersion9 & DeviceAttributesVersion10 & DeviceAttributesVersion11 &
|
||||||
DeviceAttributesVersion12 & DeviceAttributesVersion13 & DeviceAttributesVersion14
|
DeviceAttributesVersion12 & DeviceAttributesVersion13 & DeviceAttributesVersion14 &
|
||||||
|
DeviceAttributesVersion15
|
||||||
|
|
||||||
export type DeviceModel = Sequelize.Model<DeviceAttributes> & DeviceAttributes
|
export type DeviceModel = Sequelize.Model<DeviceAttributes> & DeviceAttributes
|
||||||
export type DeviceModelStatic = typeof Sequelize.Model & {
|
export type DeviceModelStatic = typeof Sequelize.Model & {
|
||||||
@@ -305,6 +317,26 @@ export const attributesVersion14: SequelizeAttributes<DeviceAttributesVersion14>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const attributesVersion15: SequelizeAttributes<DeviceAttributesVersion15> = {
|
||||||
|
platformType: {
|
||||||
|
type: Sequelize.STRING(maxPlatformTypeLength),
|
||||||
|
allowNull: true,
|
||||||
|
defaultValue: null,
|
||||||
|
validate: {
|
||||||
|
len: [minPlatformTypeLength, maxPlatformTypeLength]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
platformLevel: {
|
||||||
|
type: Sequelize.INTEGER,
|
||||||
|
allowNull: false,
|
||||||
|
defaultValue: minPlatformLevel,
|
||||||
|
validate: {
|
||||||
|
min: minPlatformLevel,
|
||||||
|
max: maxPlatformLevel
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export const attributes: SequelizeAttributes<DeviceAttributes> = {
|
export const attributes: SequelizeAttributes<DeviceAttributes> = {
|
||||||
...attributesVersion1,
|
...attributesVersion1,
|
||||||
...attributesVersion2,
|
...attributesVersion2,
|
||||||
@@ -319,7 +351,8 @@ export const attributes: SequelizeAttributes<DeviceAttributes> = {
|
|||||||
...attributesVersion11,
|
...attributesVersion11,
|
||||||
...attributesVersion12,
|
...attributesVersion12,
|
||||||
...attributesVersion13,
|
...attributesVersion13,
|
||||||
...attributesVersion14
|
...attributesVersion14,
|
||||||
|
...attributesVersion15
|
||||||
}
|
}
|
||||||
|
|
||||||
export const createDeviceModel = (sequelize: Sequelize.Sequelize): DeviceModelStatic => sequelize.define('Device', attributes) as DeviceModelStatic
|
export const createDeviceModel = (sequelize: Sequelize.Sequelize): DeviceModelStatic => sequelize.define('Device', attributes) as DeviceModelStatic
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* server component for the TimeLimit App
|
* server component for the TimeLimit App
|
||||||
* Copyright (C) 2019 - 2022 Jonas Lochmann
|
* Copyright (C) 2019 - 2024 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
|
||||||
@@ -26,7 +26,7 @@ export const config = {
|
|||||||
expireTimeRounding: 1000 * 60 * 15
|
expireTimeRounding: 1000 * 60 * 15
|
||||||
}
|
}
|
||||||
|
|
||||||
export function calculateExpireTime(now: bigint): BigInt {
|
export function calculateExpireTime(now: bigint): bigint {
|
||||||
const expireBaseTime = now + BigInt(config.expireDelay)
|
const expireBaseTime = now + BigInt(config.expireDelay)
|
||||||
const expireTime = expireBaseTime - expireBaseTime % BigInt(config.expireTimeRounding) + BigInt(config.expireTimeRounding)
|
const expireTime = expireBaseTime - expireBaseTime % BigInt(config.expireTimeRounding) + BigInt(config.expireTimeRounding)
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,46 @@
|
|||||||
|
/*
|
||||||
|
* server component for the TimeLimit App
|
||||||
|
* Copyright (C) 2019 - 2023 Jonas Lochmann
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, version 3 of the License.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { QueryInterface, Sequelize, Transaction } from 'sequelize'
|
||||||
|
import { attributesVersion15 as deviceAttributes } from '../../device'
|
||||||
|
|
||||||
|
export async function up (queryInterface: QueryInterface, sequelize: Sequelize) {
|
||||||
|
await sequelize.transaction({
|
||||||
|
type: Transaction.TYPES.EXCLUSIVE
|
||||||
|
}, async (transaction) => {
|
||||||
|
await queryInterface.addColumn('Devices', 'platformType', {
|
||||||
|
...deviceAttributes.platformType
|
||||||
|
}, {
|
||||||
|
transaction
|
||||||
|
})
|
||||||
|
|
||||||
|
await queryInterface.addColumn('Devices', 'platformLevel', {
|
||||||
|
...deviceAttributes.platformLevel
|
||||||
|
}, {
|
||||||
|
transaction
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function down (queryInterface: QueryInterface, sequelize: Sequelize) {
|
||||||
|
await sequelize.transaction({
|
||||||
|
type: Transaction.TYPES.EXCLUSIVE
|
||||||
|
}, async (transaction) => {
|
||||||
|
await queryInterface.removeColumn('Devices', 'platformLevel', { transaction })
|
||||||
|
await queryInterface.removeColumn('Devices', 'platformType', { transaction })
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
/*
|
||||||
|
* server component for the TimeLimit App
|
||||||
|
* Copyright (C) 2019 - 2024 Jonas Lochmann
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, version 3 of the License.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { QueryInterface, Sequelize, Transaction } from 'sequelize'
|
||||||
|
import { attributesVersion4 as ruleAttributes } from '../../timelimitrule'
|
||||||
|
|
||||||
|
export async function up (queryInterface: QueryInterface, sequelize: Sequelize) {
|
||||||
|
await sequelize.transaction({
|
||||||
|
type: Transaction.TYPES.EXCLUSIVE
|
||||||
|
}, async (transaction) => {
|
||||||
|
await queryInterface.addColumn('TimelimitRules', 'expiresAt', {
|
||||||
|
...ruleAttributes.expiresAt
|
||||||
|
}, {
|
||||||
|
transaction
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function down (queryInterface: QueryInterface, sequelize: Sequelize) {
|
||||||
|
await sequelize.transaction({
|
||||||
|
type: Transaction.TYPES.EXCLUSIVE
|
||||||
|
}, async (transaction) => {
|
||||||
|
await queryInterface.removeColumn('TimelimitRules', 'expiresAt', { transaction })
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* server component for the TimeLimit App
|
* server component for the TimeLimit App
|
||||||
* Copyright (C) 2019 Jonas Lochmann
|
* Copyright (C) 2019 - 2024 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,18 +17,26 @@
|
|||||||
|
|
||||||
import { resolve } from 'path'
|
import { resolve } from 'path'
|
||||||
import { Sequelize } from 'sequelize'
|
import { Sequelize } from 'sequelize'
|
||||||
import * as Umzug from 'umzug'
|
import { Umzug, SequelizeStorage } from 'umzug'
|
||||||
|
|
||||||
export const createUmzug = (sequelize: Sequelize) => (
|
export const createUmzug = (sequelize: Sequelize) => (
|
||||||
new Umzug({
|
new Umzug({
|
||||||
storage: 'sequelize',
|
storage: new SequelizeStorage({ sequelize }),
|
||||||
storageOptions: {
|
|
||||||
sequelize
|
|
||||||
},
|
|
||||||
migrations: {
|
migrations: {
|
||||||
params: [sequelize.getQueryInterface(), sequelize],
|
glob: resolve(__dirname, '../../../build/database/migration/migrations/*.js'),
|
||||||
path: resolve(__dirname, '../../../build/database/migration/migrations'),
|
resolve: ({ name, path }) => {
|
||||||
pattern: /^\d+[\w-]+\.js$/
|
if (!path) throw new Error()
|
||||||
}
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||||
|
const migration = require(path)
|
||||||
|
|
||||||
|
return {
|
||||||
|
name,
|
||||||
|
up: async () => migration.up(sequelize.getQueryInterface(), sequelize),
|
||||||
|
down: async () => migration.down(sequelize.getQueryInterface(), sequelize),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
logger: console
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* server component for the TimeLimit App
|
* server component for the TimeLimit App
|
||||||
* Copyright (C) 2019 - 2021 Jonas Lochmann
|
* Copyright (C) 2019 - 2024 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
|
||||||
@@ -41,8 +41,13 @@ interface TimelimitRuleAttributesVersion3 {
|
|||||||
perDay: number
|
perDay: number
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface TimelimitRuleAttributesVersion4 {
|
||||||
|
expiresAt: string | null
|
||||||
|
}
|
||||||
|
|
||||||
type TimelimitRuleAttributes = TimelimitRuleAttributesVersion1 &
|
type TimelimitRuleAttributes = TimelimitRuleAttributesVersion1 &
|
||||||
TimelimitRuleAttributesVersion2 & TimelimitRuleAttributesVersion3
|
TimelimitRuleAttributesVersion2 & TimelimitRuleAttributesVersion3 &
|
||||||
|
TimelimitRuleAttributesVersion4
|
||||||
|
|
||||||
export type TimelimitRuleModel = Sequelize.Model<TimelimitRuleAttributes> & TimelimitRuleAttributes
|
export type TimelimitRuleModel = Sequelize.Model<TimelimitRuleAttributes> & TimelimitRuleAttributes
|
||||||
export type TimelimitRuleModelStatic = typeof Sequelize.Model & {
|
export type TimelimitRuleModelStatic = typeof Sequelize.Model & {
|
||||||
@@ -137,10 +142,22 @@ export const attributesVersion3: SequelizeAttributes<TimelimitRuleAttributesVers
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const attributesVersion4: SequelizeAttributes<TimelimitRuleAttributesVersion4> = {
|
||||||
|
expiresAt: {
|
||||||
|
type: Sequelize.BIGINT,
|
||||||
|
validate: {
|
||||||
|
min: 1
|
||||||
|
},
|
||||||
|
allowNull: true,
|
||||||
|
defaultValue: null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export const attributes: SequelizeAttributes<TimelimitRuleAttributes> = {
|
export const attributes: SequelizeAttributes<TimelimitRuleAttributes> = {
|
||||||
...attributesVersion1,
|
...attributesVersion1,
|
||||||
...attributesVersion2,
|
...attributesVersion2,
|
||||||
...attributesVersion3
|
...attributesVersion3,
|
||||||
|
...attributesVersion4
|
||||||
}
|
}
|
||||||
|
|
||||||
export const createTimelimitRuleModel = (sequelize: Sequelize.Sequelize): TimelimitRuleModelStatic => sequelize.define('TimelimitRule', attributes) as TimelimitRuleModelStatic
|
export const createTimelimitRuleModel = (sequelize: Sequelize.Sequelize): TimelimitRuleModelStatic => sequelize.define('TimelimitRule', attributes) as TimelimitRuleModelStatic
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* server component for the TimeLimit App
|
* server component for the TimeLimit App
|
||||||
* Copyright (C) 2019 - 2021 Jonas Lochmann
|
* Copyright (C) 2019 - 2024 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,11 +23,12 @@ import { checkMailSendLimit } from '../../util/ratelimit-authmail'
|
|||||||
import { generateAuthToken } from '../../util/token'
|
import { generateAuthToken } from '../../util/token'
|
||||||
import { createAuthTokenByMailAddress } from './index'
|
import { createAuthTokenByMailAddress } from './index'
|
||||||
|
|
||||||
export const sendLoginCode = async ({ mail, deviceAuthToken, locale, database }: {
|
export const sendLoginCode = async ({ mail, deviceAuthToken, locale, database, info }: {
|
||||||
mail: string
|
mail: string
|
||||||
deviceAuthToken?: string
|
deviceAuthToken?: string
|
||||||
locale: string
|
locale: string
|
||||||
database: Database
|
database: Database
|
||||||
|
info: Buffer
|
||||||
// no transaction here because this is directly called from an API endpoint
|
// no transaction here because this is directly called from an API endpoint
|
||||||
}): Promise<{ mailLoginToken: string }> => {
|
}): Promise<{ mailLoginToken: string }> => {
|
||||||
let deviceName = null
|
let deviceName = null
|
||||||
@@ -82,7 +83,8 @@ export const sendLoginCode = async ({ mail, deviceAuthToken, locale, database }:
|
|||||||
receiver: mail,
|
receiver: mail,
|
||||||
code,
|
code,
|
||||||
locale,
|
locale,
|
||||||
deviceName
|
deviceName,
|
||||||
|
info
|
||||||
})
|
})
|
||||||
|
|
||||||
await database.transaction(async (transaction) => {
|
await database.transaction(async (transaction) => {
|
||||||
@@ -109,7 +111,7 @@ export const signInByMailCode = async ({ mailLoginToken, receivedCode, database
|
|||||||
database: Database
|
database: Database
|
||||||
// no transaction here because this is directly called from an API endpoint
|
// no transaction here because this is directly called from an API endpoint
|
||||||
}): Promise<{ mailAuthToken: string }> => {
|
}): Promise<{ mailAuthToken: string }> => {
|
||||||
return database.transaction(async (transaction) => {
|
const result = await database.transaction(async (transaction) => {
|
||||||
const entry = await database.mailLoginToken.findOne({
|
const entry = await database.mailLoginToken.findOne({
|
||||||
where: {
|
where: {
|
||||||
mailLoginToken
|
mailLoginToken
|
||||||
@@ -127,9 +129,9 @@ export const signInByMailCode = async ({ mailLoginToken, receivedCode, database
|
|||||||
await entry.save({ transaction })
|
await entry.save({ transaction })
|
||||||
|
|
||||||
if (entry.remainingAttempts === 0) {
|
if (entry.remainingAttempts === 0) {
|
||||||
throw new Gone()
|
return () => { throw new Gone() }
|
||||||
} else {
|
} else {
|
||||||
throw new Forbidden()
|
return () => { throw new Forbidden() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -151,6 +153,8 @@ export const signInByMailCode = async ({ mailLoginToken, receivedCode, database
|
|||||||
transaction
|
transaction
|
||||||
})
|
})
|
||||||
|
|
||||||
return { mailAuthToken }
|
return () => ({ mailAuthToken })
|
||||||
})
|
})
|
||||||
|
|
||||||
|
return result()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,13 +22,22 @@ 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 { notifyClientsAboutChangesDelayed } from '../websocket'
|
import { notifyClientsAboutChangesDelayed } from '../websocket'
|
||||||
|
import { generateServerDataStatus } from '../sync/get-server-data-status'
|
||||||
|
import { EventHandler } from '../../monitoring/eventhandler'
|
||||||
|
import { ServerDataStatus } from '../../object/serverdatastatus'
|
||||||
|
import { createEmptyClientDataStatus } from '../../object/clientdatastatus'
|
||||||
|
|
||||||
export const addChildDevice = async ({ database, websocket, request }: {
|
export const addChildDevice = async ({ database, eventHandler, websocket, request }: {
|
||||||
database: Database
|
database: Database
|
||||||
|
eventHandler: EventHandler
|
||||||
websocket: WebsocketApi
|
websocket: WebsocketApi
|
||||||
request: RegisterChildDeviceRequest
|
request: RegisterChildDeviceRequest
|
||||||
// no transaction here because this is directly called from an API endpoint
|
// no transaction here because this is directly called from an API endpoint
|
||||||
}) => {
|
}): Promise<{
|
||||||
|
deviceId: string
|
||||||
|
deviceAuthToken: string
|
||||||
|
data: ServerDataStatus
|
||||||
|
}> => {
|
||||||
return database.transaction(async (transaction) => {
|
return database.transaction(async (transaction) => {
|
||||||
const entry = await database.addDeviceToken.findOne({
|
const entry = await database.addDeviceToken.findOne({
|
||||||
where: {
|
where: {
|
||||||
@@ -75,9 +84,19 @@ export const addChildDevice = async ({ database, websocket, request }: {
|
|||||||
transaction
|
transaction
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const data = await generateServerDataStatus({
|
||||||
|
database,
|
||||||
|
clientStatus: createEmptyClientDataStatus({ clientLevel: request.clientLevel || null }),
|
||||||
|
familyId: entry.familyId,
|
||||||
|
deviceId,
|
||||||
|
transaction,
|
||||||
|
eventHandler
|
||||||
|
})
|
||||||
|
|
||||||
return {
|
return {
|
||||||
deviceId,
|
deviceId,
|
||||||
deviceAuthToken
|
deviceAuthToken,
|
||||||
|
data
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,107 @@
|
|||||||
|
/*
|
||||||
|
* server component for the TimeLimit App
|
||||||
|
* Copyright (C) 2019 - 2023 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 { Unauthorized } from 'http-errors'
|
||||||
|
import { DeleteAccountPayload } from '../../api/schema'
|
||||||
|
import { Database } from '../../database'
|
||||||
|
import { sendAccountDeletedMail } from '../../util/mail'
|
||||||
|
import { WebsocketApi } from '../../websocket'
|
||||||
|
import { requireMailAndLocaleByAuthToken } from '../authentication'
|
||||||
|
import { deleteFamilies } from './delete-families'
|
||||||
|
|
||||||
|
export async function deleteAccount({ request, database, websocket }: {
|
||||||
|
request: DeleteAccountPayload
|
||||||
|
database: Database
|
||||||
|
websocket: WebsocketApi
|
||||||
|
}) {
|
||||||
|
await database.transaction(async (transaction) => {
|
||||||
|
const deviceEntryUnsafe = await database.device.findOne({
|
||||||
|
where: { deviceAuthToken: request.deviceAuthToken },
|
||||||
|
attributes: ['familyId'],
|
||||||
|
transaction
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!deviceEntryUnsafe) {
|
||||||
|
throw new Unauthorized()
|
||||||
|
}
|
||||||
|
|
||||||
|
const deviceEntry = {
|
||||||
|
familyId: deviceEntryUnsafe.familyId
|
||||||
|
}
|
||||||
|
|
||||||
|
const userEntries = (await database.user.findAll({
|
||||||
|
where: {
|
||||||
|
familyId: deviceEntry.familyId,
|
||||||
|
type: 'parent'
|
||||||
|
},
|
||||||
|
attributes: ['mail'],
|
||||||
|
transaction
|
||||||
|
})).map((item) => ({ mail: item.mail }))
|
||||||
|
|
||||||
|
const registeredMailAddresses = new Set<string>()
|
||||||
|
|
||||||
|
userEntries.forEach((item) => {
|
||||||
|
if (item.mail !== '') registeredMailAddresses.add(item.mail)
|
||||||
|
})
|
||||||
|
|
||||||
|
const authenticatedMailAddresses = new Set<string>()
|
||||||
|
|
||||||
|
for (const mailAuthToken of request.mailAuthTokens) {
|
||||||
|
const info = await requireMailAndLocaleByAuthToken({
|
||||||
|
mailAuthToken,
|
||||||
|
database,
|
||||||
|
transaction,
|
||||||
|
invalidate: true
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!registeredMailAddresses.has(info.mail)) throw new Unauthorized()
|
||||||
|
|
||||||
|
authenticatedMailAddresses.add(info.mail)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (registeredMailAddresses.size !== authenticatedMailAddresses.size) throw new Unauthorized()
|
||||||
|
|
||||||
|
registeredMailAddresses.forEach((mail) => {
|
||||||
|
if (!authenticatedMailAddresses.has(mail)) throw new Unauthorized()
|
||||||
|
})
|
||||||
|
|
||||||
|
const deviceEntries = (await database.device.findAll({
|
||||||
|
where: {
|
||||||
|
familyId: deviceEntry.familyId
|
||||||
|
},
|
||||||
|
transaction,
|
||||||
|
attributes: ['deviceAuthToken']
|
||||||
|
})).map((item) => ({ deviceAuthToken: item.deviceAuthToken }))
|
||||||
|
|
||||||
|
await deleteFamilies({ database, transaction, familiyIds: [deviceEntry.familyId] })
|
||||||
|
|
||||||
|
transaction.afterCommit(() => {
|
||||||
|
for (const device of deviceEntries) {
|
||||||
|
websocket.triggerSyncByDeviceAuthToken({
|
||||||
|
deviceAuthToken: device.deviceAuthToken,
|
||||||
|
isImportant: true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
registeredMailAddresses.forEach((receiver) => {
|
||||||
|
sendAccountDeletedMail({ receiver }).catch((ex) => {
|
||||||
|
console.warn('failure while sending account deletion confirmation', ex)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* server component for the TimeLimit App
|
* server component for the TimeLimit App
|
||||||
* Copyright (C) 2019 - 2022 Jonas Lochmann
|
* Copyright (C) 2019 - 2023 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,10 +17,11 @@
|
|||||||
|
|
||||||
import { difference } from 'lodash'
|
import { difference } from 'lodash'
|
||||||
import * as Sequelize from 'sequelize'
|
import * as Sequelize from 'sequelize'
|
||||||
import { Database } from '../../database'
|
import { Database, Transaction } from '../../database'
|
||||||
|
|
||||||
export async function deleteFamilies ({ database, familiyIds }: {
|
export async function deleteFamilies ({ database, transaction, familiyIds }: {
|
||||||
database: Database
|
database: Database
|
||||||
|
transaction: Transaction
|
||||||
familiyIds: Array<string>
|
familiyIds: Array<string>
|
||||||
// no transaction here because this should run isolated
|
// no transaction here because this should run isolated
|
||||||
}) {
|
}) {
|
||||||
@@ -28,118 +29,126 @@ export async function deleteFamilies ({ database, familiyIds }: {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
await database.transaction(async (transaction) => {
|
// category
|
||||||
// category
|
await database.category.destroy({
|
||||||
await database.category.destroy({
|
where: {
|
||||||
where: {
|
familyId: {
|
||||||
familyId: {
|
[Sequelize.Op.in]: familiyIds
|
||||||
[Sequelize.Op.in]: familiyIds
|
}
|
||||||
}
|
},
|
||||||
},
|
transaction
|
||||||
transaction
|
})
|
||||||
})
|
|
||||||
|
|
||||||
// categoryapp
|
// categoryapp
|
||||||
await database.categoryApp.destroy({
|
await database.categoryApp.destroy({
|
||||||
where: {
|
where: {
|
||||||
familyId: {
|
familyId: {
|
||||||
[Sequelize.Op.in]: familiyIds
|
[Sequelize.Op.in]: familiyIds
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
transaction
|
transaction
|
||||||
})
|
})
|
||||||
|
|
||||||
// purchase
|
// purchase
|
||||||
await database.purchase.destroy({
|
await database.purchase.destroy({
|
||||||
where: {
|
where: {
|
||||||
familyId: {
|
familyId: {
|
||||||
[Sequelize.Op.in]: familiyIds
|
[Sequelize.Op.in]: familiyIds
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
transaction
|
transaction
|
||||||
})
|
})
|
||||||
|
|
||||||
// timelimitrule
|
// timelimitrule
|
||||||
await database.timelimitRule.destroy({
|
await database.timelimitRule.destroy({
|
||||||
where: {
|
where: {
|
||||||
familyId: {
|
familyId: {
|
||||||
[Sequelize.Op.in]: familiyIds
|
[Sequelize.Op.in]: familiyIds
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
transaction
|
transaction
|
||||||
})
|
})
|
||||||
|
|
||||||
// usedtime
|
// usedtime
|
||||||
await database.usedTime.destroy({
|
await database.usedTime.destroy({
|
||||||
where: {
|
where: {
|
||||||
familyId: {
|
familyId: {
|
||||||
[Sequelize.Op.in]: familiyIds
|
[Sequelize.Op.in]: familiyIds
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
transaction
|
transaction
|
||||||
})
|
})
|
||||||
|
|
||||||
// user
|
// session durations
|
||||||
await database.user.destroy({
|
await database.sessionDuration.destroy({
|
||||||
where: {
|
where: {
|
||||||
familyId: {
|
familyId: {
|
||||||
[Sequelize.Op.in]: familiyIds
|
[Sequelize.Op.in]: familiyIds
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
transaction
|
transaction
|
||||||
})
|
})
|
||||||
|
|
||||||
// device
|
// user
|
||||||
const oldDeviceAuthTokens = (await database.device.findAll({
|
await database.user.destroy({
|
||||||
|
where: {
|
||||||
|
familyId: {
|
||||||
|
[Sequelize.Op.in]: familiyIds
|
||||||
|
}
|
||||||
|
},
|
||||||
|
transaction
|
||||||
|
})
|
||||||
|
|
||||||
|
// device
|
||||||
|
const oldDeviceAuthTokens = (await database.device.findAll({
|
||||||
|
where: {
|
||||||
|
familyId: {
|
||||||
|
[Sequelize.Op.in]: familiyIds
|
||||||
|
}
|
||||||
|
},
|
||||||
|
attributes: ['deviceAuthToken'],
|
||||||
|
transaction
|
||||||
|
})).map((item) => item.deviceAuthToken)
|
||||||
|
|
||||||
|
await database.device.destroy({
|
||||||
|
where: {
|
||||||
|
familyId: {
|
||||||
|
[Sequelize.Op.in]: familiyIds
|
||||||
|
}
|
||||||
|
},
|
||||||
|
transaction
|
||||||
|
})
|
||||||
|
|
||||||
|
// olddevice
|
||||||
|
if (oldDeviceAuthTokens.length > 0) {
|
||||||
|
const knownOldDeviceAuthTokens = (await database.oldDevice.findAll({
|
||||||
where: {
|
where: {
|
||||||
familyId: {
|
deviceAuthToken: {
|
||||||
[Sequelize.Op.in]: familiyIds
|
[Sequelize.Op.in]: oldDeviceAuthTokens
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
attributes: ['deviceAuthToken'],
|
|
||||||
transaction
|
transaction
|
||||||
})).map((item) => item.deviceAuthToken)
|
})).map((item) => item.deviceAuthToken)
|
||||||
|
|
||||||
await database.device.destroy({
|
const oldDeviceAuthTokensToAdd = difference(oldDeviceAuthTokens, knownOldDeviceAuthTokens)
|
||||||
where: {
|
|
||||||
familyId: {
|
|
||||||
[Sequelize.Op.in]: familiyIds
|
|
||||||
}
|
|
||||||
},
|
|
||||||
transaction
|
|
||||||
})
|
|
||||||
|
|
||||||
// olddevice
|
if (oldDeviceAuthTokensToAdd.length > 0) {
|
||||||
if (oldDeviceAuthTokens.length > 0) {
|
await database.oldDevice.bulkCreate(
|
||||||
const knownOldDeviceAuthTokens = (await database.oldDevice.findAll({
|
oldDeviceAuthTokensToAdd.map((item) => ({
|
||||||
where: {
|
deviceAuthToken: item
|
||||||
deviceAuthToken: {
|
})),
|
||||||
[Sequelize.Op.in]: oldDeviceAuthTokens
|
{ transaction }
|
||||||
}
|
)
|
||||||
},
|
|
||||||
transaction
|
|
||||||
})).map((item) => item.deviceAuthToken)
|
|
||||||
|
|
||||||
const oldDeviceAuthTokensToAdd = difference(oldDeviceAuthTokens, knownOldDeviceAuthTokens)
|
|
||||||
|
|
||||||
if (oldDeviceAuthTokensToAdd.length > 0) {
|
|
||||||
await database.oldDevice.bulkCreate(
|
|
||||||
oldDeviceAuthTokensToAdd.map((item) => ({
|
|
||||||
deviceAuthToken: item
|
|
||||||
})),
|
|
||||||
{ transaction }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// family
|
// family
|
||||||
await database.family.destroy({
|
await database.family.destroy({
|
||||||
where: {
|
where: {
|
||||||
familyId: {
|
familyId: {
|
||||||
[Sequelize.Op.in]: familiyIds
|
[Sequelize.Op.in]: familiyIds
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
transaction
|
transaction
|
||||||
})
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* server component for the TimeLimit App
|
* server component for the TimeLimit App
|
||||||
* Copyright (C) 2019 - 2020 Jonas Lochmann
|
* Copyright (C) 2019 - 2023 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
|
||||||
@@ -26,9 +26,12 @@ export async function deleteOldFamilies (database: Database) {
|
|||||||
if (oldFamilyIds.length > 0) {
|
if (oldFamilyIds.length > 0) {
|
||||||
const familyIdsToDelete = oldFamilyIds.slice(0, 256) /* limit to 256 families per execution */
|
const familyIdsToDelete = oldFamilyIds.slice(0, 256) /* limit to 256 families per execution */
|
||||||
|
|
||||||
await deleteFamilies({
|
await database.transaction(async (transaction) => {
|
||||||
database,
|
await deleteFamilies({
|
||||||
familiyIds: familyIdsToDelete
|
database,
|
||||||
|
transaction,
|
||||||
|
familiyIds: familyIdsToDelete
|
||||||
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* server component for the TimeLimit App
|
* server component for the TimeLimit App
|
||||||
* Copyright (C) 2019 - 2022 Jonas Lochmann
|
* Copyright (C) 2019 - 2023 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
|
||||||
@@ -66,5 +66,7 @@ export const prepareDeviceEntry = ({ familyId, userId, deviceAuthToken, deviceId
|
|||||||
isQorLater: false,
|
isQorLater: false,
|
||||||
manipulationFlags: 0,
|
manipulationFlags: 0,
|
||||||
publicKey: null,
|
publicKey: null,
|
||||||
nextKeyReplySequenceNumber: '1'
|
nextKeyReplySequenceNumber: '1',
|
||||||
|
platformType: null,
|
||||||
|
platformLevel: 0
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -16,25 +16,38 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { Conflict } from 'http-errors'
|
import { Conflict } from 'http-errors'
|
||||||
|
import { generateServerDataStatus } from '../sync/get-server-data-status'
|
||||||
import { NewDeviceInfo, PlaintextParentPassword, assertPlaintextParentPasswordValid } from '../../api/schema'
|
import { NewDeviceInfo, PlaintextParentPassword, assertPlaintextParentPasswordValid } from '../../api/schema'
|
||||||
import { Database } from '../../database'
|
import { Database } from '../../database'
|
||||||
import { maxMailNotificationFlags } from '../../database/user'
|
import { maxMailNotificationFlags } from '../../database/user'
|
||||||
|
import { EventHandler } from '../../monitoring/eventhandler'
|
||||||
|
import { ServerDataStatus } from '../../object/serverdatastatus'
|
||||||
|
import { createEmptyClientDataStatus } from '../../object/clientdatastatus'
|
||||||
import {
|
import {
|
||||||
generateAuthToken, generateFamilyId, generateIdWithinFamily, generateVersionId
|
generateAuthToken, generateFamilyId, generateIdWithinFamily, generateVersionId
|
||||||
} from '../../util/token'
|
} from '../../util/token'
|
||||||
import { requireMailAndLocaleByAuthToken } from '../authentication'
|
import { requireMailAndLocaleByAuthToken } from '../authentication'
|
||||||
import { prepareDeviceEntry } from '../device/prepare-device-entry'
|
import { prepareDeviceEntry } from '../device/prepare-device-entry'
|
||||||
|
|
||||||
export const createFamily = async ({ database, mailAuthToken, firstParentDevice, password, timeZone, parentName, deviceName }: {
|
export async function createFamily ({
|
||||||
database: Database,
|
database, eventHandler, mailAuthToken, firstParentDevice,
|
||||||
mailAuthToken: string,
|
password, timeZone, parentName, deviceName, clientLevel
|
||||||
firstParentDevice: NewDeviceInfo,
|
}: {
|
||||||
password: PlaintextParentPassword,
|
database: Database
|
||||||
timeZone: string,
|
eventHandler: EventHandler
|
||||||
parentName: string,
|
mailAuthToken: string
|
||||||
|
firstParentDevice: NewDeviceInfo
|
||||||
|
password: PlaintextParentPassword
|
||||||
|
timeZone: string
|
||||||
|
parentName: string
|
||||||
deviceName: string
|
deviceName: string
|
||||||
|
clientLevel: number | null
|
||||||
// no transaction here because this is directly called from an API endpoint
|
// no transaction here because this is directly called from an API endpoint
|
||||||
}) => {
|
}): Promise<{
|
||||||
|
deviceAuthToken: string
|
||||||
|
deviceId: string
|
||||||
|
data: ServerDataStatus
|
||||||
|
}> {
|
||||||
assertPlaintextParentPasswordValid(password)
|
assertPlaintextParentPasswordValid(password)
|
||||||
|
|
||||||
return database.transaction(async (transaction) => {
|
return database.transaction(async (transaction) => {
|
||||||
@@ -42,14 +55,14 @@ export const createFamily = async ({ database, mailAuthToken, firstParentDevice,
|
|||||||
const mailInfo = await requireMailAndLocaleByAuthToken({ database, mailAuthToken, transaction, invalidate: true })
|
const mailInfo = await requireMailAndLocaleByAuthToken({ database, mailAuthToken, transaction, invalidate: true })
|
||||||
|
|
||||||
// ensure that no family was created for this mail yet
|
// ensure that no family was created for this mail yet
|
||||||
const exisitngUserEntry = await database.user.findOne({
|
const existingUserEntry = await database.user.findOne({
|
||||||
where: {
|
where: {
|
||||||
mail: mailInfo.mail
|
mail: mailInfo.mail
|
||||||
},
|
},
|
||||||
transaction
|
transaction
|
||||||
})
|
})
|
||||||
|
|
||||||
if (exisitngUserEntry) {
|
if (existingUserEntry) {
|
||||||
throw new Conflict()
|
throw new Conflict()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -103,9 +116,19 @@ export const createFamily = async ({ database, mailAuthToken, firstParentDevice,
|
|||||||
isUserKeptSignedIn: true
|
isUserKeptSignedIn: true
|
||||||
}), { transaction })
|
}), { transaction })
|
||||||
|
|
||||||
|
const data = await generateServerDataStatus({
|
||||||
|
database,
|
||||||
|
clientStatus: createEmptyClientDataStatus({ clientLevel }),
|
||||||
|
familyId,
|
||||||
|
deviceId,
|
||||||
|
transaction,
|
||||||
|
eventHandler
|
||||||
|
})
|
||||||
|
|
||||||
return {
|
return {
|
||||||
deviceAuthToken,
|
deviceAuthToken,
|
||||||
deviceId
|
deviceId,
|
||||||
|
data
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,15 +24,21 @@ import { WebsocketApi } from '../../websocket'
|
|||||||
import { requireMailAndLocaleByAuthToken } from '../authentication'
|
import { requireMailAndLocaleByAuthToken } from '../authentication'
|
||||||
import { prepareDeviceEntry } from '../device/prepare-device-entry'
|
import { prepareDeviceEntry } from '../device/prepare-device-entry'
|
||||||
import { notifyClientsAboutChangesDelayed } from '../websocket'
|
import { notifyClientsAboutChangesDelayed } from '../websocket'
|
||||||
|
import { generateServerDataStatus } from '../sync/get-server-data-status'
|
||||||
|
import { EventHandler } from '../../monitoring/eventhandler'
|
||||||
|
import { ServerDataStatus } from '../../object/serverdatastatus'
|
||||||
|
import { createEmptyClientDataStatus } from '../../object/clientdatastatus'
|
||||||
|
|
||||||
export const signInIntoFamily = async ({ database, mailAuthToken, newDeviceInfo, deviceName, websocket }: {
|
export const signInIntoFamily = async ({ database, eventHandler, mailAuthToken, newDeviceInfo, deviceName, websocket, clientLevel }: {
|
||||||
database: Database
|
database: Database
|
||||||
|
eventHandler: EventHandler
|
||||||
mailAuthToken: string
|
mailAuthToken: string
|
||||||
newDeviceInfo: NewDeviceInfo
|
newDeviceInfo: NewDeviceInfo
|
||||||
deviceName: string
|
deviceName: string
|
||||||
websocket: WebsocketApi
|
websocket: WebsocketApi
|
||||||
|
clientLevel: number | null
|
||||||
// no transaction here because this is directly called from an API endpoint
|
// no transaction here because this is directly called from an API endpoint
|
||||||
}): Promise<{ deviceId: string; deviceAuthToken: string }> => {
|
}): Promise<{ deviceId: string; deviceAuthToken: string; data: ServerDataStatus }> => {
|
||||||
return database.transaction(async (transaction) => {
|
return database.transaction(async (transaction) => {
|
||||||
const mailInfo = await requireMailAndLocaleByAuthToken({ database, mailAuthToken, transaction, invalidate: true })
|
const mailInfo = await requireMailAndLocaleByAuthToken({ database, mailAuthToken, transaction, invalidate: true })
|
||||||
|
|
||||||
@@ -94,9 +100,19 @@ export const signInIntoFamily = async ({ database, mailAuthToken, newDeviceInfo,
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const data = await generateServerDataStatus({
|
||||||
|
database,
|
||||||
|
clientStatus: createEmptyClientDataStatus({ clientLevel }),
|
||||||
|
familyId: userEntry.familyId,
|
||||||
|
deviceId,
|
||||||
|
transaction,
|
||||||
|
eventHandler
|
||||||
|
})
|
||||||
|
|
||||||
return {
|
return {
|
||||||
deviceId,
|
deviceId,
|
||||||
deviceAuthToken
|
deviceAuthToken,
|
||||||
|
data
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* server component for the TimeLimit App
|
* server component for the TimeLimit App
|
||||||
* Copyright (C) 2019 - 2022 Jonas Lochmann
|
* Copyright (C) 2019 - 2023 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,6 +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 * as Sequelize from 'sequelize'
|
||||||
import { AddUsedTimeActionVersion2 } from '../../../../action'
|
import { AddUsedTimeActionVersion2 } from '../../../../action'
|
||||||
import { EventHandler } from '../../../../monitoring/eventhandler'
|
import { EventHandler } from '../../../../monitoring/eventhandler'
|
||||||
import { MinuteOfDay } from '../../../../util/minuteofday'
|
import { MinuteOfDay } from '../../../../util/minuteofday'
|
||||||
@@ -22,6 +23,8 @@ import { Cache } from '../cache'
|
|||||||
import { IllegalStateException, SourceDeviceNotFoundException } from '../exception/illegal-state'
|
import { IllegalStateException, SourceDeviceNotFoundException } from '../exception/illegal-state'
|
||||||
import { getRoundedTimestamp as getRoundedTimestampForUsedTime } from './addusedtime'
|
import { getRoundedTimestamp as getRoundedTimestampForUsedTime } from './addusedtime'
|
||||||
|
|
||||||
|
const tolerance = 5 * 1000 // 5 seconds
|
||||||
|
|
||||||
export const getRoundedTimestampForSessionDuration = () => {
|
export const getRoundedTimestampForSessionDuration = () => {
|
||||||
const now = Date.now()
|
const now = Date.now()
|
||||||
|
|
||||||
@@ -129,11 +132,32 @@ export async function dispatchAddUsedTimeVersion2 ({ deviceId, action, cache, ev
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
const oldTime: number | null = await cache.database.usedTime.aggregate(
|
||||||
|
'usedTime',
|
||||||
|
'MAX',
|
||||||
|
{
|
||||||
|
where: {
|
||||||
|
familyId: cache.familyId,
|
||||||
|
categoryId: item.categoryId,
|
||||||
|
dayOfEpoch: action.dayOfEpoch,
|
||||||
|
startMinuteOfDay: {
|
||||||
|
[Sequelize.Op.gte]: start
|
||||||
|
},
|
||||||
|
endMinuteOfDay: {
|
||||||
|
[Sequelize.Op.lte]: end
|
||||||
|
}
|
||||||
|
},
|
||||||
|
transaction: cache.transaction
|
||||||
|
}
|
||||||
|
) || 0
|
||||||
|
|
||||||
|
if (oldTime !== null && typeof oldTime !== 'number') throw new Error()
|
||||||
|
|
||||||
await cache.database.usedTime.create({
|
await cache.database.usedTime.create({
|
||||||
familyId: cache.familyId,
|
familyId: cache.familyId,
|
||||||
categoryId: item.categoryId,
|
categoryId: item.categoryId,
|
||||||
dayOfEpoch: action.dayOfEpoch,
|
dayOfEpoch: action.dayOfEpoch,
|
||||||
usedTime: Math.max(0, Math.min(item.timeToAdd, lengthInMs)),
|
usedTime: Math.max(0, Math.min(oldTime + item.timeToAdd, lengthInMs)),
|
||||||
lastUpdate: roundedTimestampForUsedTime,
|
lastUpdate: roundedTimestampForUsedTime,
|
||||||
startMinuteOfDay: start,
|
startMinuteOfDay: start,
|
||||||
endMinuteOfDay: end
|
endMinuteOfDay: end
|
||||||
@@ -168,6 +192,39 @@ export async function dispatchAddUsedTimeVersion2 ({ deviceId, action, cache, ev
|
|||||||
transaction: cache.transaction
|
transaction: cache.transaction
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const oldDuration: () => Promise<number> = async () => {
|
||||||
|
const fittingDurationItems = await cache.database.sessionDuration.findAll({
|
||||||
|
where: {
|
||||||
|
familyId: cache.familyId,
|
||||||
|
categoryId: item.categoryId,
|
||||||
|
startMinuteOfDay: {
|
||||||
|
[Sequelize.Op.gte]: limit.start
|
||||||
|
},
|
||||||
|
endMinuteOfDay: {
|
||||||
|
[Sequelize.Op.lte]: limit.end
|
||||||
|
},
|
||||||
|
maxSessionDuration: {
|
||||||
|
[Sequelize.Op.gte]: limit.duration
|
||||||
|
},
|
||||||
|
sessionPauseDuration: {
|
||||||
|
[Sequelize.Op.lte]: limit.pause
|
||||||
|
}
|
||||||
|
},
|
||||||
|
transaction: cache.transaction
|
||||||
|
})
|
||||||
|
|
||||||
|
const fittingDurationItemsLastUsageFiltered =
|
||||||
|
hasTrustedTimestamp ?
|
||||||
|
fittingDurationItems.filter((it) => {
|
||||||
|
action.trustedTimestamp - item.timeToAdd <=
|
||||||
|
parseInt(it.lastUsage, 10) + it.sessionPauseDuration - tolerance
|
||||||
|
}) : fittingDurationItems
|
||||||
|
|
||||||
|
return fittingDurationItemsLastUsageFiltered
|
||||||
|
.map((it) => it.lastSessionDuration)
|
||||||
|
.reduce((a, b) => Math.max(a, b), 0)
|
||||||
|
}
|
||||||
|
|
||||||
if (oldItem) {
|
if (oldItem) {
|
||||||
let extendSession: boolean
|
let extendSession: boolean
|
||||||
|
|
||||||
@@ -188,14 +245,13 @@ export async function dispatchAddUsedTimeVersion2 ({ deviceId, action, cache, ev
|
|||||||
* Due to this, a session is reset if it would be over in a few seconds, too.
|
* Due to this, a session is reset if it would be over in a few seconds, too.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const tolerance = 5 * 1000 // 5 seconds
|
|
||||||
const timeWhenStartingCurrentUsage = action.trustedTimestamp - item.timeToAdd
|
const timeWhenStartingCurrentUsage = action.trustedTimestamp - item.timeToAdd
|
||||||
const nextSessionStart = parseInt(oldItem.lastUsage, 10) + oldItem.sessionPauseDuration - tolerance
|
const nextSessionStart = parseInt(oldItem.lastUsage, 10) + oldItem.sessionPauseDuration - tolerance
|
||||||
|
|
||||||
extendSession = timeWhenStartingCurrentUsage <= nextSessionStart
|
extendSession = timeWhenStartingCurrentUsage <= nextSessionStart
|
||||||
}
|
}
|
||||||
|
|
||||||
oldItem.lastSessionDuration = extendSession ? oldItem.lastSessionDuration + item.timeToAdd : item.timeToAdd
|
oldItem.lastSessionDuration = extendSession ? oldItem.lastSessionDuration + item.timeToAdd : await oldDuration() + item.timeToAdd
|
||||||
oldItem.roundedLastUpdate = roundedTimestampForSessionDuration
|
oldItem.roundedLastUpdate = roundedTimestampForSessionDuration
|
||||||
|
|
||||||
if (hasTrustedTimestamp) {
|
if (hasTrustedTimestamp) {
|
||||||
@@ -215,7 +271,7 @@ export async function dispatchAddUsedTimeVersion2 ({ deviceId, action, cache, ev
|
|||||||
endMinuteOfDay: limit.end,
|
endMinuteOfDay: limit.end,
|
||||||
// end of primary key
|
// end of primary key
|
||||||
lastUsage: action.trustedTimestamp.toString(10),
|
lastUsage: action.trustedTimestamp.toString(10),
|
||||||
lastSessionDuration: item.timeToAdd,
|
lastSessionDuration: await oldDuration() + item.timeToAdd,
|
||||||
roundedLastUpdate: roundedTimestampForSessionDuration
|
roundedLastUpdate: roundedTimestampForSessionDuration
|
||||||
}, { transaction: cache.transaction })
|
}, { transaction: cache.transaction })
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* server component for the TimeLimit App
|
* server component for the TimeLimit App
|
||||||
* Copyright (C) 2019 - 2022 Jonas Lochmann
|
* Copyright (C) 2019 - 2023 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
|
||||||
@@ -155,6 +155,18 @@ export async function dispatchUpdateDeviceStatus ({ deviceId, action, cache }: {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (action.platformType !== undefined) {
|
||||||
|
if (action.platformType !== deviceEntry.platformType) {
|
||||||
|
deviceEntry.platformType = action.platformType
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (action.platformLevel !== undefined) {
|
||||||
|
if (action.platformLevel !== deviceEntry.platformLevel) {
|
||||||
|
deviceEntry.platformLevel = action.platformLevel
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
const effectiveManipulationFlags = action.addedManipulationFlags & DeviceManipulationFlags.ALL
|
const effectiveManipulationFlags = action.addedManipulationFlags & DeviceManipulationFlags.ALL
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* server component for the TimeLimit App
|
* server component for the TimeLimit App
|
||||||
* Copyright (C) 2019 - 2022 Jonas Lochmann
|
* Copyright (C) 2019 - 2024 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
|
||||||
@@ -55,7 +55,8 @@ export async function dispatchCreateTimeLimitRule ({ action, cache, fromChildSel
|
|||||||
endMinuteOfDay: action.rule.end,
|
endMinuteOfDay: action.rule.end,
|
||||||
sessionDurationMilliseconds: action.rule.sessionDurationMilliseconds,
|
sessionDurationMilliseconds: action.rule.sessionDurationMilliseconds,
|
||||||
sessionPauseMilliseconds: action.rule.sessionPauseMilliseconds,
|
sessionPauseMilliseconds: action.rule.sessionPauseMilliseconds,
|
||||||
perDay: action.rule.perDay ? 1 : 0
|
perDay: action.rule.perDay ? 1 : 0,
|
||||||
|
expiresAt: action.rule.expiresAt ? action.rule.expiresAt.toString() : null
|
||||||
}, { transaction: cache.transaction })
|
}, { transaction: cache.transaction })
|
||||||
|
|
||||||
cache.categoriesWithModifiedTimeLimitRules.add(action.rule.categoryId)
|
cache.categoriesWithModifiedTimeLimitRules.add(action.rule.categoryId)
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* server component for the TimeLimit App
|
* server component for the TimeLimit App
|
||||||
* Copyright (C) 2019 - 2022 Jonas Lochmann
|
* Copyright (C) 2019 - 2024 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
|
||||||
@@ -81,7 +81,8 @@ export async function dispatchUpdateTimelimitRule ({
|
|||||||
action.sessionDurationMilliseconds <= ruleEntry.sessionDurationMilliseconds &&
|
action.sessionDurationMilliseconds <= ruleEntry.sessionDurationMilliseconds &&
|
||||||
action.sessionPauseMilliseconds >= ruleEntry.sessionPauseMilliseconds
|
action.sessionPauseMilliseconds >= ruleEntry.sessionPauseMilliseconds
|
||||||
)) &&
|
)) &&
|
||||||
(!action.perDay || ruleEntry.perDay || countOldAffectedDays <= 1)
|
(!action.perDay || ruleEntry.perDay || countOldAffectedDays <= 1) &&
|
||||||
|
(action.expiresAt === undefined || (ruleEntry.expiresAt !== null && action.expiresAt >= parseInt(ruleEntry.expiresAt, 10)))
|
||||||
|
|
||||||
if (!isAtLeastAsStrictAsPreviously) {
|
if (!isAtLeastAsStrictAsPreviously) {
|
||||||
throw new CanNotRelaxRestrictionsSelfLimitException()
|
throw new CanNotRelaxRestrictionsSelfLimitException()
|
||||||
@@ -96,6 +97,7 @@ export async function dispatchUpdateTimelimitRule ({
|
|||||||
ruleEntry.sessionDurationMilliseconds = action.sessionDurationMilliseconds
|
ruleEntry.sessionDurationMilliseconds = action.sessionDurationMilliseconds
|
||||||
ruleEntry.sessionPauseMilliseconds = action.sessionPauseMilliseconds
|
ruleEntry.sessionPauseMilliseconds = action.sessionPauseMilliseconds
|
||||||
ruleEntry.perDay = action.perDay ? 1 : 0
|
ruleEntry.perDay = action.perDay ? 1 : 0
|
||||||
|
ruleEntry.expiresAt = action.expiresAt ? action.expiresAt.toString() : null
|
||||||
|
|
||||||
await ruleEntry.save({ transaction: cache.transaction })
|
await ruleEntry.save({ transaction: cache.transaction })
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* server component for the TimeLimit App
|
* server component for the TimeLimit App
|
||||||
* Copyright (C) 2019 - 2020 Jonas Lochmann
|
* Copyright (C) 2019 - 2024 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
|
||||||
@@ -45,7 +45,8 @@ export async function getRules ({ database, transaction, categoryIdsToSyncRules,
|
|||||||
'endMinuteOfDay',
|
'endMinuteOfDay',
|
||||||
'sessionDurationMilliseconds',
|
'sessionDurationMilliseconds',
|
||||||
'sessionPauseMilliseconds',
|
'sessionPauseMilliseconds',
|
||||||
'perDay'
|
'perDay',
|
||||||
|
'expiresAt'
|
||||||
],
|
],
|
||||||
transaction
|
transaction
|
||||||
})).map((item) => ({
|
})).map((item) => ({
|
||||||
@@ -58,7 +59,8 @@ export async function getRules ({ database, transaction, categoryIdsToSyncRules,
|
|||||||
endMinuteOfDay: item.endMinuteOfDay,
|
endMinuteOfDay: item.endMinuteOfDay,
|
||||||
sessionDurationMilliseconds: item.sessionDurationMilliseconds,
|
sessionDurationMilliseconds: item.sessionDurationMilliseconds,
|
||||||
sessionPauseMilliseconds: item.sessionPauseMilliseconds,
|
sessionPauseMilliseconds: item.sessionPauseMilliseconds,
|
||||||
perDay: item.perDay
|
perDay: item.perDay,
|
||||||
|
expiresAt: item.expiresAt ? parseInt(item.expiresAt, 10) : undefined
|
||||||
}))
|
}))
|
||||||
|
|
||||||
const getCategoryRulesVersion = (categoryId: string) => (
|
const getCategoryRulesVersion = (categoryId: string) => (
|
||||||
@@ -76,7 +78,8 @@ export async function getRules ({ database, transaction, categoryIdsToSyncRules,
|
|||||||
end: item.endMinuteOfDay,
|
end: item.endMinuteOfDay,
|
||||||
session: item.sessionDurationMilliseconds,
|
session: item.sessionDurationMilliseconds,
|
||||||
pause: item.sessionPauseMilliseconds,
|
pause: item.sessionPauseMilliseconds,
|
||||||
perDay: item.perDay !== 0 ? true : false
|
perDay: item.perDay !== 0 ? true : false,
|
||||||
|
e: item.expiresAt
|
||||||
})),
|
})),
|
||||||
version: getCategoryRulesVersion(categoryId)
|
version: getCategoryRulesVersion(categoryId)
|
||||||
}))
|
}))
|
||||||
|
|||||||
@@ -66,7 +66,9 @@ export async function getDeviceList ({ database, transaction, familyEntry }: {
|
|||||||
activityLevelBlocking: item.activityLevelBlocking,
|
activityLevelBlocking: item.activityLevelBlocking,
|
||||||
qOrLater: item.isQorLater,
|
qOrLater: item.isQorLater,
|
||||||
mFlags: item.manipulationFlags,
|
mFlags: item.manipulationFlags,
|
||||||
pk: item.publicKey ? item.publicKey.toString('base64') : undefined
|
pk: item.publicKey ? item.publicKey.toString('base64') : undefined,
|
||||||
|
pType: item.platformType || undefined,
|
||||||
|
pLevel: item.platformLevel
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* server component for the TimeLimit App
|
* server component for the TimeLimit App
|
||||||
* Copyright (C) 2019 - 2022 Jonas Lochmann
|
* Copyright (C) 2019 - 2024 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
|
||||||
@@ -58,7 +58,7 @@ export const generateServerDataStatus = async ({
|
|||||||
familyEntry.hasFullVersion ? parseInt(familyEntry.fullVersionUntil, 10) : 0
|
familyEntry.hasFullVersion ? parseInt(familyEntry.fullVersionUntil, 10) : 0
|
||||||
),
|
),
|
||||||
message: await getStatusMessage({ database, transaction }) || undefined,
|
message: await getStatusMessage({ database, transaction }) || undefined,
|
||||||
apiLevel: 7
|
apiLevel: 8
|
||||||
}
|
}
|
||||||
|
|
||||||
if (familyEntry.deviceListVersion !== clientStatus.devices) {
|
if (familyEntry.deviceListVersion !== clientStatus.devices) {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* server component for the TimeLimit App
|
* server component for the TimeLimit App
|
||||||
* Copyright (C) 2019 - 2020 Jonas Lochmann
|
* Copyright (C) 2019 - 2024 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
|
||||||
@@ -29,11 +29,12 @@ export class TimelimitRule {
|
|||||||
readonly sessionDurationMilliseconds: number
|
readonly sessionDurationMilliseconds: number
|
||||||
readonly sessionPauseMilliseconds: number
|
readonly sessionPauseMilliseconds: number
|
||||||
readonly perDay: boolean
|
readonly perDay: boolean
|
||||||
|
readonly expiresAt?: number
|
||||||
|
|
||||||
constructor ({
|
constructor ({
|
||||||
ruleId, categoryId, maxTimeInMillis, dayMask, applyToExtraTimeUsage,
|
ruleId, categoryId, maxTimeInMillis, dayMask, applyToExtraTimeUsage,
|
||||||
start, end, sessionDurationMilliseconds, sessionPauseMilliseconds,
|
start, end, sessionDurationMilliseconds, sessionPauseMilliseconds,
|
||||||
perDay
|
perDay, expiresAt
|
||||||
}: {
|
}: {
|
||||||
ruleId: string
|
ruleId: string
|
||||||
categoryId: string
|
categoryId: string
|
||||||
@@ -45,6 +46,7 @@ export class TimelimitRule {
|
|||||||
sessionDurationMilliseconds: number
|
sessionDurationMilliseconds: number
|
||||||
sessionPauseMilliseconds: number
|
sessionPauseMilliseconds: number
|
||||||
perDay: boolean
|
perDay: boolean
|
||||||
|
expiresAt?: number
|
||||||
}) {
|
}) {
|
||||||
this.ruleId = ruleId
|
this.ruleId = ruleId
|
||||||
this.categoryId = categoryId
|
this.categoryId = categoryId
|
||||||
@@ -56,6 +58,7 @@ export class TimelimitRule {
|
|||||||
this.sessionDurationMilliseconds = sessionDurationMilliseconds
|
this.sessionDurationMilliseconds = sessionDurationMilliseconds
|
||||||
this.sessionPauseMilliseconds = sessionPauseMilliseconds
|
this.sessionPauseMilliseconds = sessionPauseMilliseconds
|
||||||
this.perDay = perDay
|
this.perDay = perDay
|
||||||
|
this.expiresAt = expiresAt
|
||||||
|
|
||||||
assertIdWithinFamily(ruleId)
|
assertIdWithinFamily(ruleId)
|
||||||
assertIdWithinFamily(categoryId)
|
assertIdWithinFamily(categoryId)
|
||||||
@@ -88,6 +91,14 @@ export class TimelimitRule {
|
|||||||
if (sessionDurationMilliseconds < 0 || sessionPauseMilliseconds < 0) {
|
if (sessionDurationMilliseconds < 0 || sessionPauseMilliseconds < 0) {
|
||||||
throw new ParseTimeLimitRuleException()
|
throw new ParseTimeLimitRuleException()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (expiresAt !== undefined) {
|
||||||
|
if (!Number.isSafeInteger(expiresAt)) throw new ParseTimeLimitRuleException()
|
||||||
|
|
||||||
|
if (expiresAt <= 0) {
|
||||||
|
throw new ParseTimeLimitRuleException()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
serialize = (): SerializedTimeLimitRule => ({
|
serialize = (): SerializedTimeLimitRule => ({
|
||||||
@@ -99,10 +110,11 @@ export class TimelimitRule {
|
|||||||
start: this.start,
|
start: this.start,
|
||||||
end: this.end,
|
end: this.end,
|
||||||
pause: this.sessionPauseMilliseconds,
|
pause: this.sessionPauseMilliseconds,
|
||||||
dur: this.sessionDurationMilliseconds
|
dur: this.sessionDurationMilliseconds,
|
||||||
|
e: this.expiresAt
|
||||||
})
|
})
|
||||||
|
|
||||||
static parse = ({ ruleId, categoryId, time, days, extraTime, start, end, dur, pause, perDay }: SerializedTimeLimitRule) => (
|
static parse = ({ ruleId, categoryId, time, days, extraTime, start, end, dur, pause, perDay, e }: SerializedTimeLimitRule) => (
|
||||||
new TimelimitRule({
|
new TimelimitRule({
|
||||||
ruleId,
|
ruleId,
|
||||||
categoryId,
|
categoryId,
|
||||||
@@ -113,7 +125,8 @@ export class TimelimitRule {
|
|||||||
end: end ?? MinuteOfDay.MAX,
|
end: end ?? MinuteOfDay.MAX,
|
||||||
sessionDurationMilliseconds: dur ?? 0,
|
sessionDurationMilliseconds: dur ?? 0,
|
||||||
sessionPauseMilliseconds: pause ?? 0,
|
sessionPauseMilliseconds: pause ?? 0,
|
||||||
perDay: perDay ?? false
|
perDay: perDay ?? false,
|
||||||
|
expiresAt: e
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -129,6 +142,7 @@ export interface SerializedTimeLimitRule {
|
|||||||
dur?: number
|
dur?: number
|
||||||
pause?: number
|
pause?: number
|
||||||
perDay?: boolean
|
perDay?: boolean
|
||||||
|
e?: number // expiresAt
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ParseTimeLimitRuleException extends Error {}
|
export class ParseTimeLimitRuleException extends Error {}
|
||||||
|
|||||||
@@ -28,6 +28,18 @@ export interface ClientDataStatus {
|
|||||||
u2f?: string // last u2f list version
|
u2f?: string // last u2f list version
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function createEmptyClientDataStatus({ clientLevel }: {
|
||||||
|
clientLevel: number | null
|
||||||
|
}): ClientDataStatus {
|
||||||
|
return {
|
||||||
|
devices: '',
|
||||||
|
apps: {},
|
||||||
|
categories: {},
|
||||||
|
users: '',
|
||||||
|
clientLevel: clientLevel || undefined
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export type ClientDataStatusApps = {[key: string]: string} // installedAppsVersionsByDeviceId
|
export type ClientDataStatusApps = {[key: string]: string} // installedAppsVersionsByDeviceId
|
||||||
export type ClientDataStatusCategories = {[key: string]: CategoryDataStatus}
|
export type ClientDataStatusCategories = {[key: string]: CategoryDataStatus}
|
||||||
export type ClientDataStatusDevicesExtended = {[key: string]: DeviceDataStatus}
|
export type ClientDataStatusDevicesExtended = {[key: string]: DeviceDataStatus}
|
||||||
|
|||||||
@@ -103,6 +103,8 @@ export interface ServerDeviceData {
|
|||||||
qOrLater: boolean
|
qOrLater: boolean
|
||||||
mFlags: number // manipulation flags
|
mFlags: number // manipulation flags
|
||||||
pk?: string // public key
|
pk?: string // public key
|
||||||
|
pType?: string
|
||||||
|
pLevel: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ServerUpdatedCategoryBaseData {
|
export interface ServerUpdatedCategoryBaseData {
|
||||||
|
|||||||
+33
-7
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* server component for the TimeLimit App
|
* server component for the TimeLimit App
|
||||||
* Copyright (C) 2019 - 2022 Jonas Lochmann
|
* Copyright (C) 2019 - 2024 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
|
||||||
@@ -45,9 +45,10 @@ function createMailTemplateSender (templateName: string) {
|
|||||||
const textTemplate = compileTemplate('text.ejs')
|
const textTemplate = compileTemplate('text.ejs')
|
||||||
const htmlTemplate = compileTemplate('html.ejs')
|
const htmlTemplate = compileTemplate('html.ejs')
|
||||||
|
|
||||||
const sendMail = async ({ receiver, params }: {
|
const sendMail = async ({ receiver, params, info }: {
|
||||||
receiver: string
|
receiver: string
|
||||||
params: object
|
params: object
|
||||||
|
info?: Buffer
|
||||||
}) => {
|
}) => {
|
||||||
if (!mailTransport) {
|
if (!mailTransport) {
|
||||||
throw new Error('can not send mails without mail config and without NODE_ENV=development')
|
throw new Error('can not send mails without mail config and without NODE_ENV=development')
|
||||||
@@ -56,6 +57,9 @@ function createMailTemplateSender (templateName: string) {
|
|||||||
const subject = subjectTemplate(params).replace(/\n/g, ' ')
|
const subject = subjectTemplate(params).replace(/\n/g, ' ')
|
||||||
const text = textTemplate(params)
|
const text = textTemplate(params)
|
||||||
const html = htmlTemplate(params)
|
const html = htmlTemplate(params)
|
||||||
|
const headers: {[key: string]: string} = info ? {
|
||||||
|
'X-TlInfo': info.toString('base64')
|
||||||
|
} : {}
|
||||||
|
|
||||||
await new Promise<void>((resolve, reject) => {
|
await new Promise<void>((resolve, reject) => {
|
||||||
mailTransport.sendMail({
|
mailTransport.sendMail({
|
||||||
@@ -63,7 +67,8 @@ function createMailTemplateSender (templateName: string) {
|
|||||||
to: receiver,
|
to: receiver,
|
||||||
subject,
|
subject,
|
||||||
text,
|
text,
|
||||||
html
|
html,
|
||||||
|
headers
|
||||||
}, (err, info) => {
|
}, (err, info) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
reject(err)
|
reject(err)
|
||||||
@@ -72,7 +77,10 @@ function createMailTemplateSender (templateName: string) {
|
|||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
const data = (info as any).message
|
const data = (info as any).message
|
||||||
|
|
||||||
console.log(JSON.stringify(JSON.parse(data), null, 2))
|
console.log(JSON.stringify({
|
||||||
|
...JSON.parse(data),
|
||||||
|
params
|
||||||
|
}, null, 2))
|
||||||
}
|
}
|
||||||
|
|
||||||
resolve()
|
resolve()
|
||||||
@@ -87,9 +95,13 @@ function createMailTemplateSender (templateName: string) {
|
|||||||
const loginMailSender = createMailTemplateSender('login')
|
const loginMailSender = createMailTemplateSender('login')
|
||||||
|
|
||||||
export const sendAuthenticationMail = async ({
|
export const sendAuthenticationMail = async ({
|
||||||
receiver, code, locale, deviceName
|
receiver, code, locale, deviceName, info
|
||||||
}: {
|
}: {
|
||||||
receiver: string, code: string, locale: string, deviceName: string | null
|
receiver: string
|
||||||
|
code: string
|
||||||
|
locale: string
|
||||||
|
deviceName: string | null
|
||||||
|
info: Buffer
|
||||||
}) => {
|
}) => {
|
||||||
await loginMailSender.sendMail({
|
await loginMailSender.sendMail({
|
||||||
receiver,
|
receiver,
|
||||||
@@ -102,7 +114,8 @@ export const sendAuthenticationMail = async ({
|
|||||||
deviceName,
|
deviceName,
|
||||||
deviceNameIntro: locale === 'de' ? 'Die Anmeldung wurde am Gerät' : 'The login was attempted at the device',
|
deviceNameIntro: locale === 'de' ? 'Die Anmeldung wurde am Gerät' : 'The login was attempted at the device',
|
||||||
deviceNameOutro: locale === 'de' ? 'versucht.' : '.'
|
deviceNameOutro: locale === 'de' ? 'versucht.' : '.'
|
||||||
}
|
},
|
||||||
|
info
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -190,6 +203,19 @@ export const sendPasswordRecoveryUsedMail = async ({ receiver, locale }: {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const accountDeletedMailSender = createMailTemplateSender('account-deleted')
|
||||||
|
|
||||||
|
export const sendAccountDeletedMail = async ({ receiver }: {
|
||||||
|
receiver: string
|
||||||
|
}) => {
|
||||||
|
await accountDeletedMailSender.sendMail({
|
||||||
|
receiver,
|
||||||
|
params: {
|
||||||
|
mailimprint
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
function getMailSecurityText (locale: string) {
|
function getMailSecurityText (locale: string) {
|
||||||
if (locale === 'de') {
|
if (locale === 'de') {
|
||||||
return 'Achten Sie darauf, dass Ihr Kind/Ihre Kinder keinen Zugang zu der E-Mail-Adresse hat/haben, die Sie bei TimeLimit angegeben haben.'
|
return 'Achten Sie darauf, dass Ihr Kind/Ihre Kinder keinen Zugang zu der E-Mail-Adresse hat/haben, die Sie bei TimeLimit angegeben haben.'
|
||||||
|
|||||||
+2
-1
@@ -11,7 +11,8 @@
|
|||||||
"noUnusedParameters": false,
|
"noUnusedParameters": false,
|
||||||
"noImplicitReturns": true,
|
"noImplicitReturns": true,
|
||||||
"noFallthroughCasesInSwitch": true,
|
"noFallthroughCasesInSwitch": true,
|
||||||
"sourceMap": true
|
"sourceMap": true,
|
||||||
|
"skipLibCheck": true
|
||||||
},
|
},
|
||||||
"include": [
|
"include": [
|
||||||
"./src/**/*"
|
"./src/**/*"
|
||||||
|
|||||||
Reference in New Issue
Block a user