mirror of
https://codeberg.org/timelimit/timelimit-server.git
synced 2026-08-31 19:03:45 +02:00
Initial commit
This commit is contained in:
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* server component for the TimeLimit App
|
||||
* Copyright (C) 2019 Jonas Lochmann
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { assertNonEmptyListWithoutDuplicates } from '../util/list'
|
||||
import { assertIdWithinFamily } from '../util/token'
|
||||
import { ParentAction } from './basetypes'
|
||||
|
||||
export class AddCategoryAppsAction extends ParentAction {
|
||||
readonly categoryId: string
|
||||
readonly packageNames: Array<string>
|
||||
|
||||
constructor ({ categoryId, packageNames }: {categoryId: string, packageNames: Array<string>}) {
|
||||
super()
|
||||
|
||||
assertIdWithinFamily(categoryId)
|
||||
assertNonEmptyListWithoutDuplicates(packageNames)
|
||||
|
||||
this.categoryId = categoryId
|
||||
this.packageNames = packageNames
|
||||
}
|
||||
|
||||
serialize = (): SerializedAddCategoryAppsAction => ({
|
||||
type: 'ADD_CATEGORY_APPS',
|
||||
categoryId: this.categoryId,
|
||||
packageNames: this.packageNames
|
||||
})
|
||||
|
||||
static parse = ({ categoryId, packageNames }: SerializedAddCategoryAppsAction) => (
|
||||
new AddCategoryAppsAction({ categoryId, packageNames })
|
||||
)
|
||||
}
|
||||
|
||||
export interface SerializedAddCategoryAppsAction {
|
||||
type: 'ADD_CATEGORY_APPS'
|
||||
categoryId: string
|
||||
packageNames: Array<string>
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* server component for the TimeLimit App
|
||||
* Copyright (C) 2019 Jonas Lochmann
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { InstalledApp, SerializedInstalledApp } from '../model/installedapp'
|
||||
import { assertNonEmptyListWithoutDuplicates } from '../util/list'
|
||||
import { AppLogicAction } from './basetypes'
|
||||
|
||||
export class AddInstalledAppsAction extends AppLogicAction {
|
||||
readonly apps: Array<InstalledApp>
|
||||
|
||||
constructor ({ apps }: {apps: Array<InstalledApp>}) {
|
||||
super()
|
||||
|
||||
assertNonEmptyListWithoutDuplicates(apps.map((app) => app.packageName))
|
||||
|
||||
this.apps = apps
|
||||
}
|
||||
|
||||
serialize = (): SerializedAddInstalledAppsAction => ({
|
||||
type: 'ADD_INSTALLED_APPS',
|
||||
apps: this.apps.map((app) => app.serialize())
|
||||
})
|
||||
|
||||
static parse = ({ apps }: SerializedAddInstalledAppsAction) => (
|
||||
new AddInstalledAppsAction({
|
||||
apps: apps.map((app) => InstalledApp.parse(app))
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
export interface SerializedAddInstalledAppsAction {
|
||||
type: 'ADD_INSTALLED_APPS'
|
||||
apps: Array<SerializedInstalledApp>
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
* server component for the TimeLimit App
|
||||
* Copyright (C) 2019 Jonas Lochmann
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { assertIdWithinFamily } from '../util/token'
|
||||
import { AppLogicAction } from './basetypes'
|
||||
|
||||
export class AddUsedTimeAction extends AppLogicAction {
|
||||
readonly categoryId: string
|
||||
readonly dayOfEpoch: number
|
||||
readonly timeToAdd: number
|
||||
readonly extraTimeToSubtract: number
|
||||
|
||||
constructor ({ categoryId, dayOfEpoch, timeToAdd, extraTimeToSubtract }: {
|
||||
categoryId: string
|
||||
dayOfEpoch: number
|
||||
timeToAdd: number
|
||||
extraTimeToSubtract: number
|
||||
}) {
|
||||
super()
|
||||
|
||||
assertIdWithinFamily(categoryId)
|
||||
|
||||
if (dayOfEpoch < 0 || (!Number.isSafeInteger(dayOfEpoch))) {
|
||||
throw new Error('illegal dayOfEpoch')
|
||||
}
|
||||
|
||||
if (timeToAdd < 0 || (!Number.isSafeInteger(timeToAdd))) {
|
||||
throw new Error('illegal timeToAdd')
|
||||
}
|
||||
|
||||
if (extraTimeToSubtract < 0 || (!Number.isSafeInteger(extraTimeToSubtract))) {
|
||||
throw new Error('illegal extra time to subtract')
|
||||
}
|
||||
|
||||
this.categoryId = categoryId
|
||||
this.dayOfEpoch = dayOfEpoch
|
||||
this.timeToAdd = timeToAdd
|
||||
this.extraTimeToSubtract = extraTimeToSubtract
|
||||
}
|
||||
|
||||
serialize = (): SerializedAddUsedTimeAction => ({
|
||||
type: 'ADD_USED_TIME',
|
||||
categoryId: this.categoryId,
|
||||
day: this.dayOfEpoch,
|
||||
timeToAdd: this.timeToAdd,
|
||||
extraTimeToSubtract: this.extraTimeToSubtract
|
||||
})
|
||||
|
||||
static parse = ({ categoryId, day, timeToAdd, extraTimeToSubtract }: SerializedAddUsedTimeAction) => (
|
||||
new AddUsedTimeAction({
|
||||
categoryId,
|
||||
dayOfEpoch: day,
|
||||
timeToAdd,
|
||||
extraTimeToSubtract
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
export interface SerializedAddUsedTimeAction {
|
||||
type: 'ADD_USED_TIME'
|
||||
categoryId: string
|
||||
day: number
|
||||
timeToAdd: number
|
||||
extraTimeToSubtract: number
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
* server component for the TimeLimit App
|
||||
* Copyright (C) 2019 Jonas Lochmann
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { assertParentPasswordValid, ParentPassword } from '../api/schema'
|
||||
import { assertIdWithinFamily } from '../util/token'
|
||||
import { ParentAction } from './basetypes'
|
||||
|
||||
export class AddUserAction extends ParentAction {
|
||||
readonly userId: string
|
||||
readonly name: string
|
||||
readonly userType: 'parent' | 'child'
|
||||
readonly password?: ParentPassword
|
||||
readonly timeZone: string
|
||||
|
||||
constructor ({ userId, name, userType, password, timeZone }: {
|
||||
userId: string
|
||||
name: string
|
||||
userType: 'parent' | 'child'
|
||||
password?: ParentPassword
|
||||
timeZone: string
|
||||
}) {
|
||||
super()
|
||||
|
||||
assertIdWithinFamily(userId)
|
||||
|
||||
this.userId = userId
|
||||
this.name = name
|
||||
this.userType = userType
|
||||
this.password = password
|
||||
this.timeZone = timeZone
|
||||
|
||||
if (userType === 'parent') {
|
||||
if (!password) {
|
||||
throw new Error('parent users must have got an password')
|
||||
}
|
||||
}
|
||||
|
||||
if (password) {
|
||||
assertParentPasswordValid(password)
|
||||
}
|
||||
}
|
||||
|
||||
serialize = (): SerializedAddUserAction => ({
|
||||
type: 'ADD_USER',
|
||||
name: this.name,
|
||||
userType: this.userType,
|
||||
userId: this.userId,
|
||||
password: this.password,
|
||||
timeZone: this.timeZone
|
||||
})
|
||||
|
||||
static parse = ({ name, userId, userType, password, timeZone }: SerializedAddUserAction) => (
|
||||
new AddUserAction({
|
||||
name,
|
||||
userId,
|
||||
userType,
|
||||
password,
|
||||
timeZone
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
export interface SerializedAddUserAction {
|
||||
type: 'ADD_USER'
|
||||
name: string
|
||||
userType: 'parent' | 'child'
|
||||
userId: string
|
||||
password?: ParentPassword
|
||||
timeZone: string
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* server component for the TimeLimit App
|
||||
* Copyright (C) 2019 Jonas Lochmann
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
export abstract class Action {
|
||||
abstract serialize: () => object
|
||||
}
|
||||
|
||||
export abstract class AppLogicAction extends Action {}
|
||||
|
||||
export abstract class ParentAction extends Action {}
|
||||
|
||||
export abstract class ChildAction extends Action {}
|
||||
@@ -0,0 +1,126 @@
|
||||
/*
|
||||
* server component for the TimeLimit App
|
||||
* Copyright (C) 2019 Jonas Lochmann
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { createDecipheriv, createHash } from 'crypto'
|
||||
import { assertIsHexString } from '../util/hexstring'
|
||||
import { assertIdWithinFamily } from '../util/token'
|
||||
import { ParentAction } from './basetypes'
|
||||
|
||||
export class ChangeParentPasswordAction extends ParentAction {
|
||||
readonly parentUserId: string
|
||||
readonly newPasswordFirstHash: string
|
||||
readonly newPasswordSecondSalt: string
|
||||
readonly newPasswordSecondHashEncrypted: string
|
||||
readonly integrity: string
|
||||
|
||||
constructor ({ parentUserId, newPasswordFirstHash, newPasswordSecondSalt, newPasswordSecondHashEncrypted, integrity }: {
|
||||
parentUserId: string
|
||||
newPasswordFirstHash: string
|
||||
newPasswordSecondSalt: string
|
||||
newPasswordSecondHashEncrypted: string
|
||||
integrity: string
|
||||
}) {
|
||||
super()
|
||||
|
||||
assertIdWithinFamily(parentUserId)
|
||||
|
||||
if (
|
||||
(!parentUserId) ||
|
||||
(!newPasswordFirstHash) ||
|
||||
(!newPasswordSecondSalt) ||
|
||||
(!newPasswordSecondHashEncrypted) ||
|
||||
(!integrity)
|
||||
) {
|
||||
throw new Error('missing required parameter for change parent password')
|
||||
}
|
||||
|
||||
if (integrity.length !== 128) {
|
||||
throw new Error('wrong length of integrity data')
|
||||
}
|
||||
|
||||
assertIsHexString(newPasswordSecondHashEncrypted)
|
||||
assertIsHexString(integrity)
|
||||
|
||||
this.parentUserId = parentUserId
|
||||
this.newPasswordFirstHash = newPasswordFirstHash
|
||||
this.newPasswordSecondSalt = newPasswordSecondSalt
|
||||
this.newPasswordSecondHashEncrypted = newPasswordSecondHashEncrypted
|
||||
this.integrity = integrity
|
||||
}
|
||||
|
||||
serialize = (): SerializedChangeParentPasswordAction => ({
|
||||
type: 'CHANGE_PARENT_PASSWORD',
|
||||
userId: this.parentUserId,
|
||||
hash: this.newPasswordFirstHash,
|
||||
secondSalt: this.newPasswordSecondSalt,
|
||||
secondHashEncrypted: this.newPasswordSecondHashEncrypted,
|
||||
integrity: this.integrity
|
||||
})
|
||||
|
||||
static parse = ({ userId, hash, secondSalt, secondHashEncrypted, integrity }: SerializedChangeParentPasswordAction) => (
|
||||
new ChangeParentPasswordAction({
|
||||
parentUserId: userId,
|
||||
newPasswordFirstHash: hash,
|
||||
newPasswordSecondSalt: secondSalt,
|
||||
newPasswordSecondHashEncrypted: secondHashEncrypted,
|
||||
integrity
|
||||
})
|
||||
)
|
||||
|
||||
assertIntegrityValid ({ oldPasswordSecondHash }: {oldPasswordSecondHash: string}) {
|
||||
const integrityData = oldPasswordSecondHash +
|
||||
this.parentUserId +
|
||||
this.newPasswordFirstHash +
|
||||
this.newPasswordSecondSalt +
|
||||
this.newPasswordSecondHashEncrypted
|
||||
|
||||
const expected = createHash('sha512').update(integrityData).digest('hex')
|
||||
|
||||
if (expected !== this.integrity) {
|
||||
throw new Error('invalid integrity for change parent password action')
|
||||
}
|
||||
}
|
||||
|
||||
decryptSecondHash ({ oldPasswordSecondHash }: {oldPasswordSecondHash: string}) {
|
||||
if (this.newPasswordSecondHashEncrypted.length <= 70) {
|
||||
throw new Error('wrong length of the new password')
|
||||
}
|
||||
|
||||
const ivHex = this.newPasswordSecondHashEncrypted.substring(0, 32)
|
||||
const salt = this.newPasswordSecondHashEncrypted.substring(32, 64)
|
||||
const encryptedData = this.newPasswordSecondHashEncrypted.substring(64)
|
||||
|
||||
const keyData = oldPasswordSecondHash + salt
|
||||
const key = createHash('sha512').update(keyData).digest().slice(0, 16)
|
||||
|
||||
const decipher = createDecipheriv('aes-128-ctr', key, Buffer.from(ivHex, 'hex'))
|
||||
decipher.setAutoPadding(false)
|
||||
|
||||
const decryptedSecondHash = decipher.update(Buffer.from(encryptedData, 'hex')).toString() + decipher.final().toString()
|
||||
|
||||
return decryptedSecondHash
|
||||
}
|
||||
}
|
||||
|
||||
export interface SerializedChangeParentPasswordAction {
|
||||
type: 'CHANGE_PARENT_PASSWORD'
|
||||
userId: string
|
||||
hash: string
|
||||
secondSalt: string
|
||||
secondHashEncrypted: string
|
||||
integrity: string
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* server component for the TimeLimit App
|
||||
* Copyright (C) 2019 Jonas Lochmann
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { assertParentPasswordValid, ParentPassword } from '../api/schema'
|
||||
import { ChildAction } from './basetypes'
|
||||
|
||||
export class ChildChangePasswordAction extends ChildAction {
|
||||
readonly password: ParentPassword
|
||||
|
||||
constructor ({ password }: {
|
||||
password: ParentPassword
|
||||
}) {
|
||||
super()
|
||||
|
||||
assertParentPasswordValid(password)
|
||||
|
||||
this.password = password
|
||||
}
|
||||
|
||||
serialize = (): SerializedChildChangePasswordAction => ({
|
||||
type: 'CHILD_CHANGE_PASSWORD',
|
||||
password: this.password
|
||||
})
|
||||
|
||||
static parse = ({ password }: SerializedChildChangePasswordAction) => (
|
||||
new ChildChangePasswordAction({ password })
|
||||
)
|
||||
}
|
||||
|
||||
export interface SerializedChildChangePasswordAction {
|
||||
type: 'CHILD_CHANGE_PASSWORD'
|
||||
password: ParentPassword
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* server component for the TimeLimit App
|
||||
* Copyright (C) 2019 Jonas Lochmann
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { ChildAction } from './basetypes'
|
||||
|
||||
export class ChildSignInAction extends ChildAction {
|
||||
constructor () {
|
||||
super()
|
||||
}
|
||||
|
||||
serialize = (): SerializedChildSignInAction => ({
|
||||
type: 'CHILD_SIGN_IN'
|
||||
})
|
||||
|
||||
static parse = (action: SerializedChildSignInAction) => (
|
||||
new ChildSignInAction()
|
||||
)
|
||||
}
|
||||
|
||||
export interface SerializedChildSignInAction {
|
||||
type: 'CHILD_SIGN_IN'
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* server component for the TimeLimit App
|
||||
* Copyright (C) 2019 Jonas Lochmann
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { assertIdWithinFamily } from '../util/token'
|
||||
import { ParentAction } from './basetypes'
|
||||
|
||||
export class CreateCategoryAction extends ParentAction {
|
||||
readonly categoryId: string
|
||||
readonly childId: string
|
||||
readonly title: string
|
||||
|
||||
constructor ({ categoryId, childId, title }: {categoryId: string, childId: string, title: string}) {
|
||||
super()
|
||||
|
||||
assertIdWithinFamily(categoryId)
|
||||
assertIdWithinFamily(childId)
|
||||
|
||||
this.categoryId = categoryId
|
||||
this.childId = childId
|
||||
this.title = title
|
||||
}
|
||||
|
||||
serialize = (): SerializedCreateCategoryAction => ({
|
||||
type: 'CREATE_CATEGORY',
|
||||
childId: this.childId,
|
||||
categoryId: this.categoryId,
|
||||
title: this.title
|
||||
})
|
||||
|
||||
static parse = ({ childId, categoryId, title }: SerializedCreateCategoryAction) => (
|
||||
new CreateCategoryAction({ childId, categoryId, title })
|
||||
)
|
||||
}
|
||||
|
||||
export interface SerializedCreateCategoryAction {
|
||||
type: 'CREATE_CATEGORY'
|
||||
childId: string
|
||||
categoryId: string
|
||||
title: string
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* server component for the TimeLimit App
|
||||
* Copyright (C) 2019 Jonas Lochmann
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { SerializedTimeLimitRule, TimelimitRule } from '../model/timelimitrule'
|
||||
import { ParentAction } from './basetypes'
|
||||
|
||||
export class CreateTimeLimitRuleAction extends ParentAction {
|
||||
rule: TimelimitRule
|
||||
|
||||
constructor ({ rule }: {rule: TimelimitRule}) {
|
||||
super()
|
||||
|
||||
this.rule = rule
|
||||
}
|
||||
|
||||
serialize = (): SerializedCreateTimelimtRuleAction => ({
|
||||
type: 'CREATE_TIMELIMIT_RULE',
|
||||
rule: this.rule.serialize()
|
||||
})
|
||||
|
||||
static parse = ({ rule }: SerializedCreateTimelimtRuleAction) => (
|
||||
new CreateTimeLimitRuleAction({
|
||||
rule: TimelimitRule.parse(rule)
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
export interface SerializedCreateTimelimtRuleAction {
|
||||
type: 'CREATE_TIMELIMIT_RULE'
|
||||
rule: SerializedTimeLimitRule
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* server component for the TimeLimit App
|
||||
* Copyright (C) 2019 Jonas Lochmann
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { assertIdWithinFamily } from '../util/token'
|
||||
import { ParentAction } from './basetypes'
|
||||
|
||||
export class DeleteCategoryAction extends ParentAction {
|
||||
readonly categoryId: string
|
||||
|
||||
constructor ({ categoryId }: {categoryId: string}) {
|
||||
super()
|
||||
|
||||
assertIdWithinFamily(categoryId)
|
||||
|
||||
this.categoryId = categoryId
|
||||
}
|
||||
|
||||
serialize = (): SerializedDeleteCategoryAction => ({
|
||||
type: 'DELETE_CATEGORY',
|
||||
categoryId: this.categoryId
|
||||
})
|
||||
|
||||
static parse = ({ categoryId }: SerializedDeleteCategoryAction) => (
|
||||
new DeleteCategoryAction({ categoryId })
|
||||
)
|
||||
}
|
||||
|
||||
export interface SerializedDeleteCategoryAction {
|
||||
type: 'DELETE_CATEGORY'
|
||||
categoryId: string
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* server component for the TimeLimit App
|
||||
* Copyright (C) 2019 Jonas Lochmann
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { assertIdWithinFamily } from '../util/token'
|
||||
import { ParentAction } from './basetypes'
|
||||
|
||||
export class DeleteTimeLimitRuleAction extends ParentAction {
|
||||
readonly ruleId: string
|
||||
|
||||
constructor ({ ruleId }: {ruleId: string}) {
|
||||
super()
|
||||
|
||||
assertIdWithinFamily(ruleId)
|
||||
|
||||
this.ruleId = ruleId
|
||||
}
|
||||
|
||||
serialize = (): SerializedDeleteTimeLimitRuleAction => ({
|
||||
type: 'DELETE_TIMELIMIT_RULE',
|
||||
ruleId: this.ruleId
|
||||
})
|
||||
|
||||
static parse = ({ ruleId }: SerializedDeleteTimeLimitRuleAction) => (
|
||||
new DeleteTimeLimitRuleAction({ ruleId })
|
||||
)
|
||||
}
|
||||
|
||||
export interface SerializedDeleteTimeLimitRuleAction {
|
||||
type: 'DELETE_TIMELIMIT_RULE'
|
||||
ruleId: string
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
/*
|
||||
* server component for the TimeLimit App
|
||||
* Copyright (C) 2019 Jonas Lochmann
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { assertIdWithinFamily } from '../util/token'
|
||||
import { ParentAction } from './basetypes'
|
||||
|
||||
export class IgnoreManipulationAction extends ParentAction {
|
||||
readonly deviceId: string
|
||||
readonly ignoreDeviceAdminManipulation: boolean
|
||||
readonly ignoreDeviceAdminManipulationAttempt: boolean
|
||||
readonly ignoreAppDowngrade: boolean
|
||||
readonly ignoreNotificationAccessManipulation: boolean
|
||||
readonly ignoreUsageStatsAccessManipulation: boolean
|
||||
readonly ignoreDidReboot: boolean
|
||||
readonly ignoreHadManipulation: boolean
|
||||
|
||||
constructor ({
|
||||
deviceId, ignoreDeviceAdminManipulation, ignoreDeviceAdminManipulationAttempt,
|
||||
ignoreAppDowngrade, ignoreNotificationAccessManipulation, ignoreUsageStatsAccessManipulation,
|
||||
ignoreDidReboot, ignoreHadManipulation
|
||||
}: {
|
||||
deviceId: string
|
||||
ignoreDeviceAdminManipulation: boolean
|
||||
ignoreDeviceAdminManipulationAttempt: boolean
|
||||
ignoreAppDowngrade: boolean
|
||||
ignoreNotificationAccessManipulation: boolean
|
||||
ignoreUsageStatsAccessManipulation: boolean
|
||||
ignoreDidReboot: boolean
|
||||
ignoreHadManipulation: boolean
|
||||
}) {
|
||||
super()
|
||||
|
||||
assertIdWithinFamily(deviceId)
|
||||
|
||||
this.deviceId = deviceId
|
||||
this.ignoreDeviceAdminManipulation = ignoreDeviceAdminManipulation
|
||||
this.ignoreDeviceAdminManipulationAttempt = ignoreDeviceAdminManipulationAttempt
|
||||
this.ignoreAppDowngrade = ignoreAppDowngrade
|
||||
this.ignoreNotificationAccessManipulation = ignoreNotificationAccessManipulation
|
||||
this.ignoreUsageStatsAccessManipulation = ignoreUsageStatsAccessManipulation
|
||||
this.ignoreDidReboot = ignoreDidReboot
|
||||
this.ignoreHadManipulation = ignoreHadManipulation
|
||||
}
|
||||
|
||||
serialize = (): SerializedIgnoreManipulationAction => ({
|
||||
type: 'IGNORE_MANIPULATION',
|
||||
deviceId: this.deviceId,
|
||||
admin: this.ignoreDeviceAdminManipulation,
|
||||
adminA: this.ignoreDeviceAdminManipulationAttempt,
|
||||
downgrade: this.ignoreAppDowngrade,
|
||||
notification: this.ignoreNotificationAccessManipulation,
|
||||
usageStats: this.ignoreUsageStatsAccessManipulation,
|
||||
hadManipulation: this.ignoreHadManipulation
|
||||
})
|
||||
|
||||
static parse = ({ deviceId, admin, adminA, downgrade, notification, usageStats, reboot, hadManipulation }: SerializedIgnoreManipulationAction) => (
|
||||
new IgnoreManipulationAction({
|
||||
deviceId,
|
||||
ignoreDeviceAdminManipulation: admin,
|
||||
ignoreDeviceAdminManipulationAttempt: adminA,
|
||||
ignoreAppDowngrade: downgrade,
|
||||
ignoreUsageStatsAccessManipulation: usageStats,
|
||||
ignoreNotificationAccessManipulation: notification,
|
||||
ignoreDidReboot: !!reboot,
|
||||
ignoreHadManipulation: hadManipulation
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
export interface SerializedIgnoreManipulationAction {
|
||||
type: 'IGNORE_MANIPULATION'
|
||||
deviceId: string
|
||||
admin: boolean
|
||||
adminA: boolean
|
||||
downgrade: boolean
|
||||
notification: boolean
|
||||
usageStats: boolean
|
||||
hadManipulation: boolean
|
||||
// was added at a later version
|
||||
reboot?: boolean
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* server component for the TimeLimit App
|
||||
* Copyright (C) 2019 Jonas Lochmann
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { assertIdWithinFamily } from '../util/token'
|
||||
import { ParentAction } from './basetypes'
|
||||
|
||||
export class IncrementCategoryExtraTimeAction extends ParentAction {
|
||||
readonly categoryId: string
|
||||
readonly addedExtraTime: number
|
||||
|
||||
constructor ({ categoryId, addedExtraTime }: {categoryId: string, addedExtraTime: number}) {
|
||||
super()
|
||||
|
||||
assertIdWithinFamily(categoryId)
|
||||
|
||||
if (addedExtraTime <= 0 || (!Number.isSafeInteger(addedExtraTime))) {
|
||||
throw new Error('must add some extra time with IncrementCategoryExtraTimeAction')
|
||||
}
|
||||
|
||||
this.categoryId = categoryId
|
||||
this.addedExtraTime = addedExtraTime
|
||||
}
|
||||
|
||||
serialize = (): SerializedIncrementCategoryExtraTimeAction => ({
|
||||
type: 'INCREMENT_CATEGORY_EXTRATIME',
|
||||
categoryId: this.categoryId,
|
||||
addedExtraTime: this.addedExtraTime
|
||||
})
|
||||
|
||||
static parse = ({ categoryId, addedExtraTime }: SerializedIncrementCategoryExtraTimeAction) => (
|
||||
new IncrementCategoryExtraTimeAction({ categoryId, addedExtraTime })
|
||||
)
|
||||
}
|
||||
|
||||
export interface SerializedIncrementCategoryExtraTimeAction {
|
||||
type: 'INCREMENT_CATEGORY_EXTRATIME'
|
||||
categoryId: string
|
||||
addedExtraTime: number
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* server component for the TimeLimit App
|
||||
* Copyright (C) 2019 Jonas Lochmann
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
export { AppLogicAction, ChildAction, ParentAction } from './basetypes'
|
||||
|
||||
export { AddCategoryAppsAction } from './addcategoryapps'
|
||||
export { AddUserAction } from './adduser'
|
||||
export { AddInstalledAppsAction } from './addinstalledapps'
|
||||
export { AddUsedTimeAction } from './addusedtime'
|
||||
export { ChangeParentPasswordAction } from './changeparentpassword'
|
||||
export { ChildChangePasswordAction } from './childchangepassword'
|
||||
export { ChildSignInAction } from './childsignin'
|
||||
export { CreateCategoryAction } from './createcategory'
|
||||
export { CreateTimeLimitRuleAction } from './createtimelimitrule'
|
||||
export { DeleteCategoryAction } from './deletecategory'
|
||||
export { DeleteTimeLimitRuleAction } from './deletetimelimitrule'
|
||||
export { IgnoreManipulationAction } from './ignoremanipulation'
|
||||
export { IncrementCategoryExtraTimeAction } from './incrementcategoryextratime'
|
||||
export { RemoveCategoryAppsAction } from './removecategoryapps'
|
||||
export { RemoveInstalledAppsAction } from './removeinstalledapps'
|
||||
export { RemoveUserAction } from './removeuser'
|
||||
export { RenameChildAction } from './renamechild'
|
||||
export { SetCategoryExtraTimeAction } from './setcategoryextratime'
|
||||
export { SetCategoryForUnassignedAppsAction } from './setcategoryforunassignedapps'
|
||||
export { SetChildPasswordAction } from './setchildpassword'
|
||||
export { SetConsiderRebootManipulationAction } from './setconsiderrebootmanipulation'
|
||||
export { SetDeviceDefaultUserAction } from './setdevicedefaultuser'
|
||||
export { SetDeviceDefaultUserTimeoutAction } from './setdevicedefaultusertimeout'
|
||||
export { SetDeviceUserAction } from './setdeviceuser'
|
||||
export { SetKeepSignedInAction } from './setkeepsignedin'
|
||||
export { SetParentCategoryAction } from './setparentcategory'
|
||||
export { SetRelaxPrimaryDeviceAction } from './setrelaxprimarydevice'
|
||||
export { SetSendDeviceConnected } from './setsenddeviceconnected'
|
||||
export { SetUserDisableLimitsUntilAction } from './setuserdisablelimitsuntil'
|
||||
export { SetUserTimezoneAction } from './setusertimezone'
|
||||
export { SignOutAtDeviceAction } from './signoutatdevice'
|
||||
export { TriedDisablingDeviceAdminAction } from './trieddisablingdeviceadmin'
|
||||
export { UpdateCategoryBlockedTimesAction } from './updatecategoryblockedtimes'
|
||||
export { UpdateCategoryTemporarilyBlockedAction } from './updatecategorytemporarilyblocked'
|
||||
export { UpdateCategoryTitleAction } from './updatecategorytitle'
|
||||
export { UpdateDeviceNameAction } from './updatedevicename'
|
||||
export { UpdateDeviceStatusAction } from './updatedevicestatus'
|
||||
export { UpdateNetworkTimeVerificationAction } from './updatenetworktimeverification'
|
||||
export { UpdateParentNotificationFlagsAction } from './updateparentnotificationflags'
|
||||
export { UpdateTimelimitRuleAction } from './updatetimelimitrule'
|
||||
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* server component for the TimeLimit App
|
||||
* Copyright (C) 2019 Jonas Lochmann
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { assertNonEmptyListWithoutDuplicates } from '../util/list'
|
||||
import { assertIdWithinFamily } from '../util/token'
|
||||
import { ParentAction } from './basetypes'
|
||||
|
||||
export class RemoveCategoryAppsAction extends ParentAction {
|
||||
readonly categoryId: string
|
||||
readonly packageNames: Array<string>
|
||||
|
||||
constructor ({ categoryId, packageNames }: {categoryId: string, packageNames: Array<string>}) {
|
||||
super()
|
||||
|
||||
assertIdWithinFamily(categoryId)
|
||||
assertNonEmptyListWithoutDuplicates(packageNames)
|
||||
|
||||
this.categoryId = categoryId
|
||||
this.packageNames = packageNames
|
||||
}
|
||||
|
||||
serialize = (): SerializedRemoveCategoryAppsAction => ({
|
||||
type: 'REMOVE_CATEGORY_APPS',
|
||||
categoryId: this.categoryId,
|
||||
packageNames: this.packageNames
|
||||
})
|
||||
|
||||
static parse = ({ categoryId, packageNames }: SerializedRemoveCategoryAppsAction) => (
|
||||
new RemoveCategoryAppsAction({ categoryId, packageNames })
|
||||
)
|
||||
}
|
||||
|
||||
export interface SerializedRemoveCategoryAppsAction {
|
||||
type: 'REMOVE_CATEGORY_APPS'
|
||||
categoryId: string
|
||||
packageNames: Array<string>
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* server component for the TimeLimit App
|
||||
* Copyright (C) 2019 Jonas Lochmann
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { assertNonEmptyListWithoutDuplicates } from '../util/list'
|
||||
import { AppLogicAction } from './basetypes'
|
||||
|
||||
export class RemoveInstalledAppsAction extends AppLogicAction {
|
||||
readonly packageNames: Array<string>
|
||||
|
||||
constructor ({ packageNames }: {packageNames: Array<string>}) {
|
||||
super()
|
||||
|
||||
assertNonEmptyListWithoutDuplicates(packageNames)
|
||||
|
||||
this.packageNames = packageNames
|
||||
}
|
||||
|
||||
serialize = (): SerializedRemoveInstalledAppsAction => ({
|
||||
type: 'REMOVE_INSTALLED_APPS',
|
||||
packageNames: this.packageNames
|
||||
})
|
||||
|
||||
static parse = ({ packageNames }: SerializedRemoveInstalledAppsAction) => (
|
||||
new RemoveInstalledAppsAction({ packageNames })
|
||||
)
|
||||
}
|
||||
|
||||
export interface SerializedRemoveInstalledAppsAction {
|
||||
type: 'REMOVE_INSTALLED_APPS'
|
||||
packageNames: Array<string>
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* server component for the TimeLimit App
|
||||
* Copyright (C) 2019 Jonas Lochmann
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { assertIdWithinFamily } from '../util/token'
|
||||
import { ParentAction } from './basetypes'
|
||||
|
||||
export class RemoveUserAction extends ParentAction {
|
||||
readonly userId: string
|
||||
// required for deleting parent users
|
||||
// parent users can only be removed by other parent users
|
||||
// this should be the value of sha512(userId + secondPasswordHash + 'remove').substring(0, 16)
|
||||
readonly authentication?: string
|
||||
|
||||
constructor ({ userId, authentication }: {
|
||||
userId: string
|
||||
authentication?: string
|
||||
}) {
|
||||
super()
|
||||
|
||||
assertIdWithinFamily(userId)
|
||||
|
||||
this.userId = userId
|
||||
this.authentication = authentication
|
||||
}
|
||||
|
||||
serialize = (): SerializedRemoveUserAction => ({
|
||||
type: 'REMOVE_USER',
|
||||
userId: this.userId,
|
||||
authentication: this.authentication
|
||||
})
|
||||
|
||||
static parse = ({ userId, authentication }: SerializedRemoveUserAction) => (
|
||||
new RemoveUserAction({ userId, authentication })
|
||||
)
|
||||
}
|
||||
|
||||
export interface SerializedRemoveUserAction {
|
||||
type: 'REMOVE_USER'
|
||||
userId: string
|
||||
authentication?: string
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* server component for the TimeLimit App
|
||||
* Copyright (C) 2019 Jonas Lochmann
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { assertIdWithinFamily } from '../util/token'
|
||||
import { ParentAction } from './basetypes'
|
||||
|
||||
export class RenameChildAction extends ParentAction {
|
||||
readonly childId: string
|
||||
readonly newName: string
|
||||
|
||||
constructor ({ childId, newName }: {
|
||||
childId: string
|
||||
newName: string
|
||||
}) {
|
||||
super()
|
||||
|
||||
assertIdWithinFamily(childId)
|
||||
|
||||
if (newName === '') {
|
||||
throw new Error('new name must not be empty')
|
||||
}
|
||||
|
||||
this.childId = childId
|
||||
this.newName = newName
|
||||
}
|
||||
|
||||
serialize = (): SerializedRenameChildAction => ({
|
||||
type: 'RENAME_CHILD',
|
||||
childId: this.childId,
|
||||
newName: this.newName
|
||||
})
|
||||
|
||||
static parse = ({ childId, newName }: SerializedRenameChildAction) => (
|
||||
new RenameChildAction({ childId, newName })
|
||||
)
|
||||
}
|
||||
|
||||
export interface SerializedRenameChildAction {
|
||||
type: 'RENAME_CHILD'
|
||||
childId: string
|
||||
newName: string
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* server component for the TimeLimit App
|
||||
* Copyright (C) 2019 Jonas Lochmann
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { AddInstalledAppsAction, SerializedAddInstalledAppsAction } from '../addinstalledapps'
|
||||
import { AddUsedTimeAction, SerializedAddUsedTimeAction } from '../addusedtime'
|
||||
import { AppLogicAction } from '../basetypes'
|
||||
import { RemoveInstalledAppsAction, SerializedRemoveInstalledAppsAction } from '../removeinstalledapps'
|
||||
import { SerializedSignOutAtDeviceAction, SignOutAtDeviceAction } from '../signoutatdevice'
|
||||
import { SerialiezdTriedDisablingDeviceAdminAction, TriedDisablingDeviceAdminAction } from '../trieddisablingdeviceadmin'
|
||||
import { SerializedUpdateDeviceStatusAction, UpdateDeviceStatusAction } from '../updatedevicestatus'
|
||||
|
||||
export type SerializedAppLogicAction =
|
||||
SerializedAddInstalledAppsAction |
|
||||
SerializedAddUsedTimeAction |
|
||||
SerializedRemoveInstalledAppsAction |
|
||||
SerializedSignOutAtDeviceAction |
|
||||
SerialiezdTriedDisablingDeviceAdminAction |
|
||||
SerializedUpdateDeviceStatusAction
|
||||
|
||||
export const parseAppLogicAction = (serialized: SerializedAppLogicAction): AppLogicAction => {
|
||||
if (serialized.type === 'ADD_USED_TIME') {
|
||||
return AddUsedTimeAction.parse(serialized)
|
||||
} else if (serialized.type === 'ADD_INSTALLED_APPS') {
|
||||
return AddInstalledAppsAction.parse(serialized)
|
||||
} else if (serialized.type === 'REMOVE_INSTALLED_APPS') {
|
||||
return RemoveInstalledAppsAction.parse(serialized)
|
||||
} else if (serialized.type === 'SIGN_OUT_AT_DEVICE') {
|
||||
return SignOutAtDeviceAction.parse(serialized)
|
||||
} else if (serialized.type === 'TRIED_DISABLING_DEVICE_ADMIN') {
|
||||
return new TriedDisablingDeviceAdminAction()
|
||||
} else if (serialized.type === 'UPDATE_DEVICE_STATUS') {
|
||||
return UpdateDeviceStatusAction.parse(serialized)
|
||||
} else {
|
||||
throw new Error('illegal state: unsupported type at parseAppLogicAction')
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* server component for the TimeLimit App
|
||||
* Copyright (C) 2019 Jonas Lochmann
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { ChildChangePasswordAction, SerializedChildChangePasswordAction } from '../childchangepassword'
|
||||
import { ChildSignInAction, SerializedChildSignInAction } from '../childsignin'
|
||||
|
||||
export type SerializedChildAction = SerializedChildChangePasswordAction | SerializedChildSignInAction
|
||||
|
||||
export const parseChildAction = (serialized: SerializedChildAction) => {
|
||||
if (serialized.type === 'CHILD_CHANGE_PASSWORD') {
|
||||
return ChildChangePasswordAction.parse(serialized)
|
||||
} else if (serialized.type === 'CHILD_SIGN_IN') {
|
||||
return ChildSignInAction.parse(serialized)
|
||||
} else {
|
||||
throw new Error('illegal state: unsupported type at parseChildAction')
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* server component for the TimeLimit App
|
||||
* Copyright (C) 2019 Jonas Lochmann
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
export { parseAppLogicAction, SerializedAppLogicAction } from './applogicaction'
|
||||
export { parseChildAction, SerializedChildAction } from './childaction'
|
||||
export { parseParentAction, SerializedParentAction } from './parentaction'
|
||||
@@ -0,0 +1,154 @@
|
||||
/*
|
||||
* server component for the TimeLimit App
|
||||
* Copyright (C) 2019 Jonas Lochmann
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { AddCategoryAppsAction, SerializedAddCategoryAppsAction } from '../addcategoryapps'
|
||||
import { AddUserAction, SerializedAddUserAction } from '../adduser'
|
||||
import { ParentAction } from '../basetypes'
|
||||
import { ChangeParentPasswordAction, SerializedChangeParentPasswordAction } from '../changeparentpassword'
|
||||
import { CreateCategoryAction, SerializedCreateCategoryAction } from '../createcategory'
|
||||
import { CreateTimeLimitRuleAction, SerializedCreateTimelimtRuleAction } from '../createtimelimitrule'
|
||||
import { DeleteCategoryAction, SerializedDeleteCategoryAction } from '../deletecategory'
|
||||
import { DeleteTimeLimitRuleAction, SerializedDeleteTimeLimitRuleAction } from '../deletetimelimitrule'
|
||||
import { IgnoreManipulationAction, SerializedIgnoreManipulationAction } from '../ignoremanipulation'
|
||||
import { IncrementCategoryExtraTimeAction, SerializedIncrementCategoryExtraTimeAction } from '../incrementcategoryextratime'
|
||||
import { RemoveCategoryAppsAction, SerializedRemoveCategoryAppsAction } from '../removecategoryapps'
|
||||
import { RemoveUserAction, SerializedRemoveUserAction } from '../removeuser'
|
||||
import { RenameChildAction, SerializedRenameChildAction } from '../renamechild'
|
||||
import { SerializedSetCategoryExtraTimeAction, SetCategoryExtraTimeAction } from '../setcategoryextratime'
|
||||
import { SerializedSetCategoryForUnassignedAppsAction, SetCategoryForUnassignedAppsAction } from '../setcategoryforunassignedapps'
|
||||
import { SerializedSetChildPasswordAction, SetChildPasswordAction } from '../setchildpassword'
|
||||
import { SerializedSetConsiderRebootManipulationAction, SetConsiderRebootManipulationAction } from '../setconsiderrebootmanipulation'
|
||||
import { SerializedSetDeviceDefaultUserAction, SetDeviceDefaultUserAction } from '../setdevicedefaultuser'
|
||||
import { SerializedSetDeviceDefaultUserTimeoutAction, SetDeviceDefaultUserTimeoutAction } from '../setdevicedefaultusertimeout'
|
||||
import { SerializedSetDeviceUserAction, SetDeviceUserAction } from '../setdeviceuser'
|
||||
import { SerializedSetKeepSignedInAction, SetKeepSignedInAction } from '../setkeepsignedin'
|
||||
import { SerializedSetParentCategoryAction, SetParentCategoryAction } from '../setparentcategory'
|
||||
import { SerializedSetRelaxPrimaryDeviceAction, SetRelaxPrimaryDeviceAction } from '../setrelaxprimarydevice'
|
||||
import { SerializedSetSendDeviceConnected, SetSendDeviceConnected } from '../setsenddeviceconnected'
|
||||
import { SerializedSetUserDisableLimitsUntilAction, SetUserDisableLimitsUntilAction } from '../setuserdisablelimitsuntil'
|
||||
import { SerializedSetUserTimezoneAction, SetUserTimezoneAction } from '../setusertimezone'
|
||||
import { SerializedUpdateCategoryBlockedTimesAction, UpdateCategoryBlockedTimesAction } from '../updatecategoryblockedtimes'
|
||||
import { SerializedUpdateCategoryTemporarilyBlockedAction, UpdateCategoryTemporarilyBlockedAction } from '../updatecategorytemporarilyblocked'
|
||||
import { SerializedUpdateCategoryTitleAction, UpdateCategoryTitleAction } from '../updatecategorytitle'
|
||||
import { SerializedUpdateDeviceNameAction, UpdateDeviceNameAction } from '../updatedevicename'
|
||||
import { SerialiizedUpdateNetworkTimeVerificationAction, UpdateNetworkTimeVerificationAction } from '../updatenetworktimeverification'
|
||||
import { SerializedUpdateParentNotificationFlagsAction, UpdateParentNotificationFlagsAction } from '../updateparentnotificationflags'
|
||||
import { SerializedUpdateTimelimitRuleAction, UpdateTimelimitRuleAction } from '../updatetimelimitrule'
|
||||
|
||||
export type SerializedParentAction =
|
||||
SerializedAddCategoryAppsAction |
|
||||
SerializedAddUserAction |
|
||||
SerializedChangeParentPasswordAction |
|
||||
SerializedCreateCategoryAction |
|
||||
SerializedCreateTimelimtRuleAction |
|
||||
SerializedDeleteCategoryAction |
|
||||
SerializedDeleteTimeLimitRuleAction |
|
||||
SerializedIgnoreManipulationAction |
|
||||
SerializedIncrementCategoryExtraTimeAction |
|
||||
SerializedRemoveCategoryAppsAction |
|
||||
SerializedRemoveUserAction |
|
||||
SerializedRenameChildAction |
|
||||
SerializedSetCategoryForUnassignedAppsAction |
|
||||
SerializedSetChildPasswordAction |
|
||||
SerializedSetConsiderRebootManipulationAction |
|
||||
SerializedSetDeviceDefaultUserAction |
|
||||
SerializedSetDeviceDefaultUserTimeoutAction |
|
||||
SerializedSetCategoryExtraTimeAction |
|
||||
SerializedSetDeviceUserAction |
|
||||
SerializedSetKeepSignedInAction |
|
||||
SerializedSetParentCategoryAction |
|
||||
SerializedSetRelaxPrimaryDeviceAction |
|
||||
SerializedSetSendDeviceConnected |
|
||||
SerializedSetUserDisableLimitsUntilAction |
|
||||
SerializedSetUserTimezoneAction |
|
||||
SerializedUpdateCategoryBlockedTimesAction |
|
||||
SerializedUpdateCategoryTemporarilyBlockedAction |
|
||||
SerializedUpdateCategoryTitleAction |
|
||||
SerializedUpdateDeviceNameAction |
|
||||
SerialiizedUpdateNetworkTimeVerificationAction |
|
||||
SerializedUpdateParentNotificationFlagsAction |
|
||||
SerializedUpdateTimelimitRuleAction
|
||||
|
||||
export const parseParentAction = (action: SerializedParentAction): ParentAction => {
|
||||
if (action.type === 'ADD_CATEGORY_APPS') {
|
||||
return AddCategoryAppsAction.parse(action)
|
||||
} else if (action.type === 'ADD_USER') {
|
||||
return AddUserAction.parse(action)
|
||||
} else if (action.type === 'CHANGE_PARENT_PASSWORD') {
|
||||
return ChangeParentPasswordAction.parse(action)
|
||||
} else if (action.type === 'CREATE_CATEGORY') {
|
||||
return CreateCategoryAction.parse(action)
|
||||
} else if (action.type === 'CREATE_TIMELIMIT_RULE') {
|
||||
return CreateTimeLimitRuleAction.parse(action)
|
||||
} else if (action.type === 'DELETE_CATEGORY') {
|
||||
return DeleteCategoryAction.parse(action)
|
||||
} else if (action.type === 'DELETE_TIMELIMIT_RULE') {
|
||||
return DeleteTimeLimitRuleAction.parse(action)
|
||||
} else if (action.type === 'IGNORE_MANIPULATION') {
|
||||
return IgnoreManipulationAction.parse(action)
|
||||
} else if (action.type === 'INCREMENT_CATEGORY_EXTRATIME') {
|
||||
return IncrementCategoryExtraTimeAction.parse(action)
|
||||
} else if (action.type === 'REMOVE_CATEGORY_APPS') {
|
||||
return RemoveCategoryAppsAction.parse(action)
|
||||
} else if (action.type === 'REMOVE_USER') {
|
||||
return RemoveUserAction.parse(action)
|
||||
} else if (action.type === 'RENAME_CHILD') {
|
||||
return RenameChildAction.parse(action)
|
||||
} else if (action.type === 'SET_CATEGORY_EXTRA_TIME') {
|
||||
return SetCategoryExtraTimeAction.parse(action)
|
||||
} else if (action.type === 'SET_CATEGORY_FOR_UNASSIGNED_APPS') {
|
||||
return SetCategoryForUnassignedAppsAction.parse(action)
|
||||
} else if (action.type === 'SET_CHILD_PASSWORD') {
|
||||
return SetChildPasswordAction.parse(action)
|
||||
} else if (action.type === 'SET_CONSIDER_REBOOT_MANIPULATION') {
|
||||
return SetConsiderRebootManipulationAction.parse(action)
|
||||
} else if (action.type === 'SET_DEVICE_DEFAULT_USER') {
|
||||
return SetDeviceDefaultUserAction.parse(action)
|
||||
} else if (action.type === 'SET_DEVICE_DEFAULT_USER_TIMEOUT') {
|
||||
return SetDeviceDefaultUserTimeoutAction.parse(action)
|
||||
} else if (action.type === 'SET_DEVICE_USER') {
|
||||
return SetDeviceUserAction.parse(action)
|
||||
} else if (action.type === 'SET_KEEP_SIGNED_IN') {
|
||||
return SetKeepSignedInAction.parse(action)
|
||||
} else if (action.type === 'SET_PARENT_CATEGORY') {
|
||||
return SetParentCategoryAction.parse(action)
|
||||
} else if (action.type === 'SET_RELAX_PRIMARY_DEVICE') {
|
||||
return SetRelaxPrimaryDeviceAction.parse(action)
|
||||
} else if (action.type === 'SET_SEND_DEVICE_CONNECTED') {
|
||||
return SetSendDeviceConnected.parse(action)
|
||||
} else if (action.type === 'SET_USER_DISABLE_LIMITS_UNTIL') {
|
||||
return SetUserDisableLimitsUntilAction.parse(action)
|
||||
} else if (action.type === 'SET_USER_TIMEZONE') {
|
||||
return SetUserTimezoneAction.parse(action)
|
||||
} else if (action.type === 'UPDATE_CATEGORY_BLOCKED_TIMES') {
|
||||
return UpdateCategoryBlockedTimesAction.parse(action)
|
||||
} else if (action.type === 'UPDATE_CATEGORY_TITLE') {
|
||||
return UpdateCategoryTitleAction.parse(action)
|
||||
} else if (action.type === 'UPDATE_CATEGORY_TEMPORARILY_BLOCKED') {
|
||||
return UpdateCategoryTemporarilyBlockedAction.parse(action)
|
||||
} else if (action.type === 'UPDATE_DEVICE_NAME') {
|
||||
return UpdateDeviceNameAction.parse(action)
|
||||
} else if (action.type === 'UPDATE_NETWORK_TIME_VERIFICATION') {
|
||||
return UpdateNetworkTimeVerificationAction.parse(action)
|
||||
} else if (action.type === 'UPDATE_PARENT_NOTIFICATION_FLAGS') {
|
||||
return UpdateParentNotificationFlagsAction.parse(action)
|
||||
} else if (action.type === 'UPDATE_TIMELIMIT_RULE') {
|
||||
return UpdateTimelimitRuleAction.parse(action)
|
||||
} else {
|
||||
throw new Error('illegal state: invalid type for action at parseParentAction')
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* server component for the TimeLimit App
|
||||
* Copyright (C) 2019 Jonas Lochmann
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { assertIdWithinFamily } from '../util/token'
|
||||
import { ParentAction } from './basetypes'
|
||||
|
||||
export class SetCategoryExtraTimeAction extends ParentAction {
|
||||
readonly categoryId: string
|
||||
readonly newExtraTime: number
|
||||
|
||||
constructor ({ categoryId, newExtraTime }: {categoryId: string, newExtraTime: number}) {
|
||||
super()
|
||||
|
||||
assertIdWithinFamily(categoryId)
|
||||
|
||||
if (newExtraTime < 0 || (!Number.isSafeInteger(newExtraTime))) {
|
||||
throw Error('newExtraTime must be >= 0')
|
||||
}
|
||||
|
||||
this.categoryId = categoryId
|
||||
this.newExtraTime = newExtraTime
|
||||
}
|
||||
|
||||
serialize = (): SerializedSetCategoryExtraTimeAction => ({
|
||||
type: 'SET_CATEGORY_EXTRA_TIME',
|
||||
categoryId: this.categoryId,
|
||||
newExtraTime: this.newExtraTime
|
||||
})
|
||||
|
||||
static parse = ({ categoryId, newExtraTime }: SerializedSetCategoryExtraTimeAction) => (
|
||||
new SetCategoryExtraTimeAction({ categoryId, newExtraTime })
|
||||
)
|
||||
}
|
||||
|
||||
export interface SerializedSetCategoryExtraTimeAction {
|
||||
type: 'SET_CATEGORY_EXTRA_TIME'
|
||||
categoryId: string
|
||||
newExtraTime: number
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* server component for the TimeLimit App
|
||||
* Copyright (C) 2019 Jonas Lochmann
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { assertIdWithinFamily } from '../util/token'
|
||||
import { ParentAction } from './basetypes'
|
||||
|
||||
export class SetCategoryForUnassignedAppsAction extends ParentAction {
|
||||
readonly childId: string
|
||||
readonly categoryId: string
|
||||
|
||||
constructor ({ childId, categoryId }: {
|
||||
childId: string
|
||||
categoryId: string
|
||||
}) {
|
||||
super()
|
||||
|
||||
assertIdWithinFamily(childId)
|
||||
|
||||
if (categoryId !== '') {
|
||||
assertIdWithinFamily(categoryId)
|
||||
}
|
||||
|
||||
this.childId = childId
|
||||
this.categoryId = categoryId
|
||||
}
|
||||
|
||||
serialize = (): SerializedSetCategoryForUnassignedAppsAction => ({
|
||||
type: 'SET_CATEGORY_FOR_UNASSIGNED_APPS',
|
||||
childId: this.childId,
|
||||
categoryId: this.categoryId
|
||||
})
|
||||
|
||||
static parse = ({ childId, categoryId }: SerializedSetCategoryForUnassignedAppsAction) => (
|
||||
new SetCategoryForUnassignedAppsAction({ childId, categoryId })
|
||||
)
|
||||
}
|
||||
|
||||
export interface SerializedSetCategoryForUnassignedAppsAction {
|
||||
type: 'SET_CATEGORY_FOR_UNASSIGNED_APPS'
|
||||
childId: string
|
||||
categoryId: string
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* server component for the TimeLimit App
|
||||
* Copyright (C) 2019 Jonas Lochmann
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { assertParentPasswordValid, ParentPassword } from '../api/schema'
|
||||
import { assertIdWithinFamily } from '../util/token'
|
||||
import { ParentAction } from './basetypes'
|
||||
|
||||
export class SetChildPasswordAction extends ParentAction {
|
||||
readonly childUserId: string
|
||||
readonly newPassword: ParentPassword
|
||||
|
||||
constructor ({ childUserId, newPassword }: {
|
||||
childUserId: string
|
||||
newPassword: ParentPassword
|
||||
}) {
|
||||
super()
|
||||
|
||||
assertIdWithinFamily(childUserId)
|
||||
assertParentPasswordValid(newPassword)
|
||||
|
||||
this.childUserId = childUserId
|
||||
this.newPassword = newPassword
|
||||
}
|
||||
|
||||
serialize = (): SerializedSetChildPasswordAction => ({
|
||||
type: 'SET_CHILD_PASSWORD',
|
||||
childId: this.childUserId,
|
||||
newPassword: this.newPassword
|
||||
})
|
||||
|
||||
static parse = ({ childId, newPassword }: SerializedSetChildPasswordAction) => (
|
||||
new SetChildPasswordAction({
|
||||
childUserId: childId,
|
||||
newPassword
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
export interface SerializedSetChildPasswordAction {
|
||||
type: 'SET_CHILD_PASSWORD'
|
||||
childId: string
|
||||
newPassword: ParentPassword
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* server component for the TimeLimit App
|
||||
* Copyright (C) 2019 Jonas Lochmann
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { assertIdWithinFamily } from '../util/token'
|
||||
import { ParentAction } from './basetypes'
|
||||
|
||||
export class SetConsiderRebootManipulationAction extends ParentAction {
|
||||
readonly deviceId: string
|
||||
readonly enable: boolean
|
||||
|
||||
constructor ({ deviceId, enable }: {deviceId: string, enable: boolean}) {
|
||||
super()
|
||||
|
||||
assertIdWithinFamily(deviceId)
|
||||
|
||||
this.deviceId = deviceId
|
||||
this.enable = enable
|
||||
}
|
||||
|
||||
serialize = (): SerializedSetConsiderRebootManipulationAction => ({
|
||||
type: 'SET_CONSIDER_REBOOT_MANIPULATION',
|
||||
deviceId: this.deviceId,
|
||||
enable: this.enable
|
||||
})
|
||||
|
||||
static parse = ({ deviceId, enable }: SerializedSetConsiderRebootManipulationAction) => (
|
||||
new SetConsiderRebootManipulationAction({ deviceId, enable })
|
||||
)
|
||||
}
|
||||
|
||||
export interface SerializedSetConsiderRebootManipulationAction {
|
||||
type: 'SET_CONSIDER_REBOOT_MANIPULATION'
|
||||
deviceId: string
|
||||
enable: boolean
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* server component for the TimeLimit App
|
||||
* Copyright (C) 2019 Jonas Lochmann
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { assertIdWithinFamily } from '../util/token'
|
||||
import { ParentAction } from './basetypes'
|
||||
|
||||
export class SetDeviceDefaultUserAction extends ParentAction {
|
||||
readonly deviceId: string
|
||||
readonly defaultUserId: string
|
||||
|
||||
constructor ({ deviceId, defaultUserId }: {
|
||||
deviceId: string
|
||||
defaultUserId: string
|
||||
}) {
|
||||
super()
|
||||
|
||||
assertIdWithinFamily(deviceId)
|
||||
|
||||
if (defaultUserId !== '') {
|
||||
assertIdWithinFamily(defaultUserId)
|
||||
}
|
||||
|
||||
this.deviceId = deviceId
|
||||
this.defaultUserId = defaultUserId
|
||||
}
|
||||
|
||||
serialize = (): SerializedSetDeviceDefaultUserAction => ({
|
||||
type: 'SET_DEVICE_DEFAULT_USER',
|
||||
deviceId: this.deviceId,
|
||||
defaultUserId: this.defaultUserId
|
||||
})
|
||||
|
||||
static parse = ({ deviceId, defaultUserId }: SerializedSetDeviceDefaultUserAction) => (
|
||||
new SetDeviceDefaultUserAction({ deviceId, defaultUserId })
|
||||
)
|
||||
}
|
||||
|
||||
export interface SerializedSetDeviceDefaultUserAction {
|
||||
type: 'SET_DEVICE_DEFAULT_USER'
|
||||
deviceId: string
|
||||
defaultUserId: string
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* server component for the TimeLimit App
|
||||
* Copyright (C) 2019 Jonas Lochmann
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { assertIdWithinFamily } from '../util/token'
|
||||
import { ParentAction } from './basetypes'
|
||||
|
||||
export class SetDeviceDefaultUserTimeoutAction extends ParentAction {
|
||||
readonly deviceId: string
|
||||
readonly timeout: number
|
||||
|
||||
constructor ({ deviceId, timeout }: {
|
||||
deviceId: string
|
||||
timeout: number
|
||||
}) {
|
||||
super()
|
||||
|
||||
assertIdWithinFamily(deviceId)
|
||||
|
||||
if ((!Number.isInteger(timeout)) || (timeout < 0)) {
|
||||
throw new Error('timeout must be a non-negative integer')
|
||||
}
|
||||
|
||||
this.deviceId = deviceId
|
||||
this.timeout = timeout
|
||||
}
|
||||
|
||||
serialize = (): SerializedSetDeviceDefaultUserTimeoutAction => ({
|
||||
type: 'SET_DEVICE_DEFAULT_USER_TIMEOUT',
|
||||
deviceId: this.deviceId,
|
||||
timeout: this.timeout
|
||||
})
|
||||
|
||||
static parse = ({ deviceId, timeout }: SerializedSetDeviceDefaultUserTimeoutAction) => (
|
||||
new SetDeviceDefaultUserTimeoutAction({ deviceId, timeout })
|
||||
)
|
||||
}
|
||||
|
||||
export interface SerializedSetDeviceDefaultUserTimeoutAction {
|
||||
type: 'SET_DEVICE_DEFAULT_USER_TIMEOUT'
|
||||
deviceId: string
|
||||
timeout: number
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* server component for the TimeLimit App
|
||||
* Copyright (C) 2019 Jonas Lochmann
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { assertIdWithinFamily } from '../util/token'
|
||||
import { ParentAction } from './basetypes'
|
||||
|
||||
export class SetDeviceUserAction extends ParentAction {
|
||||
readonly deviceId: string
|
||||
readonly userId: string // user id can be ""
|
||||
|
||||
constructor ({ deviceId, userId }: {
|
||||
deviceId: string
|
||||
userId: string
|
||||
}) {
|
||||
super()
|
||||
|
||||
assertIdWithinFamily(deviceId)
|
||||
|
||||
if (userId !== '') {
|
||||
assertIdWithinFamily(userId)
|
||||
}
|
||||
|
||||
this.deviceId = deviceId
|
||||
this.userId = userId
|
||||
}
|
||||
|
||||
serialize = (): SerializedSetDeviceUserAction => ({
|
||||
type: 'SET_DEVICE_USER',
|
||||
deviceId: this.deviceId,
|
||||
userId: this.userId
|
||||
})
|
||||
|
||||
static parse = ({ deviceId, userId }: SerializedSetDeviceUserAction) => (
|
||||
new SetDeviceUserAction({ deviceId, userId })
|
||||
)
|
||||
}
|
||||
|
||||
export interface SerializedSetDeviceUserAction {
|
||||
type: 'SET_DEVICE_USER'
|
||||
deviceId: string
|
||||
userId: string
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* server component for the TimeLimit App
|
||||
* Copyright (C) 2019 Jonas Lochmann
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { assertIdWithinFamily } from '../util/token'
|
||||
import { ParentAction } from './basetypes'
|
||||
|
||||
export class SetKeepSignedInAction extends ParentAction {
|
||||
readonly deviceId: string
|
||||
readonly keepSignedIn: boolean
|
||||
|
||||
constructor ({ deviceId, keepSignedIn }: {
|
||||
deviceId: string
|
||||
keepSignedIn: boolean
|
||||
}) {
|
||||
super()
|
||||
|
||||
assertIdWithinFamily(deviceId)
|
||||
|
||||
this.deviceId = deviceId
|
||||
this.keepSignedIn = keepSignedIn
|
||||
}
|
||||
|
||||
serialize = (): SerializedSetKeepSignedInAction => ({
|
||||
type: 'SET_KEEP_SIGNED_IN',
|
||||
deviceId: this.deviceId,
|
||||
keepSignedIn: this.keepSignedIn
|
||||
})
|
||||
|
||||
static parse = ({ deviceId, keepSignedIn }: SerializedSetKeepSignedInAction) => (
|
||||
new SetKeepSignedInAction({
|
||||
deviceId,
|
||||
keepSignedIn
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
export interface SerializedSetKeepSignedInAction {
|
||||
type: 'SET_KEEP_SIGNED_IN'
|
||||
deviceId: string
|
||||
keepSignedIn: boolean
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* server component for the TimeLimit App
|
||||
* Copyright (C) 2019 Jonas Lochmann
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { assertIdWithinFamily } from '../util/token'
|
||||
import { ParentAction } from './basetypes'
|
||||
|
||||
export class SetParentCategoryAction extends ParentAction {
|
||||
readonly categoryId: string
|
||||
readonly parentCategory: string
|
||||
|
||||
constructor ({ categoryId, parentCategory }: {
|
||||
categoryId: string
|
||||
parentCategory: string
|
||||
}) {
|
||||
super()
|
||||
|
||||
assertIdWithinFamily(categoryId)
|
||||
|
||||
if (parentCategory !== '') {
|
||||
assertIdWithinFamily(parentCategory)
|
||||
}
|
||||
|
||||
this.categoryId = categoryId
|
||||
this.parentCategory = parentCategory
|
||||
}
|
||||
|
||||
serialize = (): SerializedSetParentCategoryAction => ({
|
||||
type: 'SET_PARENT_CATEGORY',
|
||||
categoryId: this.categoryId,
|
||||
parentCategory: this.parentCategory
|
||||
})
|
||||
|
||||
static parse = ({ categoryId, parentCategory }: SerializedSetParentCategoryAction) => (
|
||||
new SetParentCategoryAction({
|
||||
categoryId,
|
||||
parentCategory
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
export interface SerializedSetParentCategoryAction {
|
||||
type: 'SET_PARENT_CATEGORY'
|
||||
categoryId: string
|
||||
parentCategory: string
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* server component for the TimeLimit App
|
||||
* Copyright (C) 2019 Jonas Lochmann
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { assertIdWithinFamily } from '../util/token'
|
||||
import { ParentAction } from './basetypes'
|
||||
|
||||
export class SetRelaxPrimaryDeviceAction extends ParentAction {
|
||||
readonly userId: string
|
||||
readonly relax: boolean
|
||||
|
||||
constructor ({ userId, relax }: {
|
||||
userId: string
|
||||
relax: boolean
|
||||
}) {
|
||||
super()
|
||||
|
||||
assertIdWithinFamily(userId)
|
||||
|
||||
this.userId = userId
|
||||
this.relax = relax
|
||||
}
|
||||
|
||||
serialize = (): SerializedSetRelaxPrimaryDeviceAction => ({
|
||||
type: 'SET_RELAX_PRIMARY_DEVICE',
|
||||
userId: this.userId,
|
||||
relax: this.relax
|
||||
})
|
||||
|
||||
static parse = ({ userId, relax }: SerializedSetRelaxPrimaryDeviceAction) => (
|
||||
new SetRelaxPrimaryDeviceAction({ userId, relax })
|
||||
)
|
||||
}
|
||||
|
||||
export interface SerializedSetRelaxPrimaryDeviceAction {
|
||||
type: 'SET_RELAX_PRIMARY_DEVICE'
|
||||
userId: string
|
||||
relax: boolean
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* server component for the TimeLimit App
|
||||
* Copyright (C) 2019 Jonas Lochmann
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { assertIdWithinFamily } from '../util/token'
|
||||
import { ParentAction } from './basetypes'
|
||||
|
||||
export class SetSendDeviceConnected extends ParentAction {
|
||||
readonly deviceId: string
|
||||
readonly enable: boolean
|
||||
|
||||
constructor ({ deviceId, enable }: {
|
||||
deviceId: string
|
||||
enable: boolean
|
||||
}) {
|
||||
super()
|
||||
|
||||
assertIdWithinFamily(deviceId)
|
||||
|
||||
this.deviceId = deviceId
|
||||
this.enable = enable
|
||||
}
|
||||
|
||||
serialize = (): SerializedSetSendDeviceConnected => ({
|
||||
type: 'SET_SEND_DEVICE_CONNECTED',
|
||||
deviceId: this.deviceId,
|
||||
enable: this.enable
|
||||
})
|
||||
|
||||
static parse = ({ deviceId, enable }: SerializedSetSendDeviceConnected) => (
|
||||
new SetSendDeviceConnected({
|
||||
deviceId,
|
||||
enable
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
export interface SerializedSetSendDeviceConnected {
|
||||
type: 'SET_SEND_DEVICE_CONNECTED'
|
||||
deviceId: string
|
||||
enable: boolean
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* server component for the TimeLimit App
|
||||
* Copyright (C) 2019 Jonas Lochmann
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { assertIdWithinFamily } from '../util/token'
|
||||
import { ParentAction } from './basetypes'
|
||||
|
||||
export class SetUserDisableLimitsUntilAction extends ParentAction {
|
||||
readonly childId: string
|
||||
readonly timestamp: number
|
||||
|
||||
constructor ({ childId, timestamp }: {
|
||||
childId: string
|
||||
timestamp: number
|
||||
}) {
|
||||
super()
|
||||
|
||||
assertIdWithinFamily(childId)
|
||||
|
||||
if (timestamp < 0 || (!Number.isSafeInteger(timestamp))) {
|
||||
throw new Error('timestamp for set user disabe limits until must be >= 0')
|
||||
}
|
||||
|
||||
this.childId = childId
|
||||
this.timestamp = timestamp
|
||||
}
|
||||
|
||||
serialize = (): SerializedSetUserDisableLimitsUntilAction => ({
|
||||
type: 'SET_USER_DISABLE_LIMITS_UNTIL',
|
||||
childId: this.childId,
|
||||
time: this.timestamp
|
||||
})
|
||||
|
||||
static parse = ({ childId, time }: SerializedSetUserDisableLimitsUntilAction) => (
|
||||
new SetUserDisableLimitsUntilAction({
|
||||
childId,
|
||||
timestamp: time
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
export interface SerializedSetUserDisableLimitsUntilAction {
|
||||
type: 'SET_USER_DISABLE_LIMITS_UNTIL'
|
||||
childId: string
|
||||
time: number
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* server component for the TimeLimit App
|
||||
* Copyright (C) 2019 Jonas Lochmann
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { assertIdWithinFamily } from '../util/token'
|
||||
import { ParentAction } from './basetypes'
|
||||
|
||||
export class SetUserTimezoneAction extends ParentAction {
|
||||
readonly userId: string
|
||||
readonly timezone: string
|
||||
|
||||
constructor ({ userId, timezone }: {
|
||||
userId: string
|
||||
timezone: string
|
||||
}) {
|
||||
super()
|
||||
|
||||
assertIdWithinFamily(userId)
|
||||
|
||||
this.userId = userId
|
||||
this.timezone = timezone
|
||||
}
|
||||
|
||||
serialize = (): SerializedSetUserTimezoneAction => ({
|
||||
type: 'SET_USER_TIMEZONE',
|
||||
userId: this.userId,
|
||||
timezone: this.timezone
|
||||
})
|
||||
|
||||
static parse = ({ userId, timezone }: SerializedSetUserTimezoneAction) => (
|
||||
new SetUserTimezoneAction({ userId, timezone })
|
||||
)
|
||||
}
|
||||
|
||||
export interface SerializedSetUserTimezoneAction {
|
||||
type: 'SET_USER_TIMEZONE'
|
||||
userId: string
|
||||
timezone: string
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* server component for the TimeLimit App
|
||||
* Copyright (C) 2019 Jonas Lochmann
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { AppLogicAction } from './basetypes'
|
||||
|
||||
export class SignOutAtDeviceAction extends AppLogicAction {
|
||||
static instance = new SignOutAtDeviceAction()
|
||||
|
||||
private constructor () {
|
||||
super()
|
||||
}
|
||||
|
||||
serialize = (): SerializedSignOutAtDeviceAction => ({
|
||||
type: 'SIGN_OUT_AT_DEVICE'
|
||||
})
|
||||
|
||||
static parse = (action: SerializedSignOutAtDeviceAction) => SignOutAtDeviceAction.instance
|
||||
}
|
||||
|
||||
export interface SerializedSignOutAtDeviceAction {
|
||||
type: 'SIGN_OUT_AT_DEVICE'
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* server component for the TimeLimit App
|
||||
* Copyright (C) 2019 Jonas Lochmann
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { AppLogicAction } from './basetypes'
|
||||
|
||||
export class TriedDisablingDeviceAdminAction extends AppLogicAction {
|
||||
constructor () {
|
||||
super()
|
||||
}
|
||||
|
||||
serialize = (): SerialiezdTriedDisablingDeviceAdminAction => ({
|
||||
type: 'TRIED_DISABLING_DEVICE_ADMIN'
|
||||
})
|
||||
}
|
||||
|
||||
export interface SerialiezdTriedDisablingDeviceAdminAction {
|
||||
type: 'TRIED_DISABLING_DEVICE_ADMIN'
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* server component for the TimeLimit App
|
||||
* Copyright (C) 2019 Jonas Lochmann
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { validateBitmask } from '../util/bitmask'
|
||||
import { assertIdWithinFamily } from '../util/token'
|
||||
import { ParentAction } from './basetypes'
|
||||
|
||||
export class UpdateCategoryBlockedTimesAction extends ParentAction {
|
||||
readonly categoryId: string
|
||||
readonly blockedTimes: string
|
||||
|
||||
constructor ({ categoryId, blockedTimes }: {
|
||||
categoryId: string
|
||||
blockedTimes: string
|
||||
}) {
|
||||
super()
|
||||
|
||||
assertIdWithinFamily(categoryId)
|
||||
validateBitmask(blockedTimes, 60 * 24 * 7 /* number of minutes per week */)
|
||||
|
||||
this.categoryId = categoryId
|
||||
this.blockedTimes = blockedTimes
|
||||
}
|
||||
|
||||
serialize = (): SerializedUpdateCategoryBlockedTimesAction => ({
|
||||
type: 'UPDATE_CATEGORY_BLOCKED_TIMES',
|
||||
categoryId: this.categoryId,
|
||||
times: this.blockedTimes
|
||||
})
|
||||
|
||||
static parse = ({ categoryId, times }: SerializedUpdateCategoryBlockedTimesAction) => (
|
||||
new UpdateCategoryBlockedTimesAction({
|
||||
categoryId,
|
||||
blockedTimes: times
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
export interface SerializedUpdateCategoryBlockedTimesAction {
|
||||
type: 'UPDATE_CATEGORY_BLOCKED_TIMES'
|
||||
categoryId: string
|
||||
times: string
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* server component for the TimeLimit App
|
||||
* Copyright (C) 2019 Jonas Lochmann
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { assertIdWithinFamily } from '../util/token'
|
||||
import { ParentAction } from './basetypes'
|
||||
|
||||
export class UpdateCategoryTemporarilyBlockedAction extends ParentAction {
|
||||
readonly categoryId: string
|
||||
readonly blocked: boolean
|
||||
|
||||
constructor ({ categoryId, blocked }: {categoryId: string, blocked: boolean}) {
|
||||
super()
|
||||
|
||||
assertIdWithinFamily(categoryId)
|
||||
|
||||
this.categoryId = categoryId
|
||||
this.blocked = blocked
|
||||
}
|
||||
|
||||
serialize = (): SerializedUpdateCategoryTemporarilyBlockedAction => ({
|
||||
type: 'UPDATE_CATEGORY_TEMPORARILY_BLOCKED',
|
||||
categoryId: this.categoryId,
|
||||
blocked: this.blocked
|
||||
})
|
||||
|
||||
static parse = ({ categoryId, blocked }: SerializedUpdateCategoryTemporarilyBlockedAction) => (
|
||||
new UpdateCategoryTemporarilyBlockedAction({ categoryId, blocked })
|
||||
)
|
||||
}
|
||||
|
||||
export interface SerializedUpdateCategoryTemporarilyBlockedAction {
|
||||
type: 'UPDATE_CATEGORY_TEMPORARILY_BLOCKED'
|
||||
categoryId: string
|
||||
blocked: boolean
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* server component for the TimeLimit App
|
||||
* Copyright (C) 2019 Jonas Lochmann
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { assertIdWithinFamily } from '../util/token'
|
||||
import { ParentAction } from './basetypes'
|
||||
|
||||
export class UpdateCategoryTitleAction extends ParentAction {
|
||||
readonly categoryId: string
|
||||
readonly newTitle: string
|
||||
|
||||
constructor ({ categoryId, newTitle }: {categoryId: string, newTitle: string}) {
|
||||
super()
|
||||
|
||||
assertIdWithinFamily(categoryId)
|
||||
|
||||
this.categoryId = categoryId
|
||||
this.newTitle = newTitle
|
||||
}
|
||||
|
||||
serialize = (): SerializedUpdateCategoryTitleAction => ({
|
||||
type: 'UPDATE_CATEGORY_TITLE',
|
||||
categoryId: this.categoryId,
|
||||
newTitle: this.newTitle
|
||||
})
|
||||
|
||||
static parse = ({ categoryId, newTitle }: SerializedUpdateCategoryTitleAction) => (
|
||||
new UpdateCategoryTitleAction({ categoryId, newTitle })
|
||||
)
|
||||
}
|
||||
|
||||
export interface SerializedUpdateCategoryTitleAction {
|
||||
type: 'UPDATE_CATEGORY_TITLE'
|
||||
categoryId: string
|
||||
newTitle: string
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* server component for the TimeLimit App
|
||||
* Copyright (C) 2019 Jonas Lochmann
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { assertIdWithinFamily } from '../util/token'
|
||||
import { ParentAction } from './basetypes'
|
||||
|
||||
export class UpdateDeviceNameAction extends ParentAction {
|
||||
readonly deviceId: string
|
||||
readonly name: string
|
||||
|
||||
constructor ({ deviceId, name }: {
|
||||
deviceId: string
|
||||
name: string
|
||||
}) {
|
||||
super()
|
||||
|
||||
this.deviceId = deviceId
|
||||
this.name = name
|
||||
|
||||
assertIdWithinFamily(deviceId)
|
||||
|
||||
if (name.trim().length === 0) {
|
||||
throw new Error('new device name must not be blank')
|
||||
}
|
||||
}
|
||||
|
||||
serialize = (): SerializedUpdateDeviceNameAction => ({
|
||||
type: 'UPDATE_DEVICE_NAME',
|
||||
deviceId: this.deviceId,
|
||||
name: this.name
|
||||
})
|
||||
|
||||
static parse = ({ deviceId, name }: SerializedUpdateDeviceNameAction) => (
|
||||
new UpdateDeviceNameAction({ deviceId, name })
|
||||
)
|
||||
}
|
||||
|
||||
export interface SerializedUpdateDeviceNameAction {
|
||||
type: 'UPDATE_DEVICE_NAME'
|
||||
deviceId: string
|
||||
name: string
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
* server component for the TimeLimit App
|
||||
* Copyright (C) 2019 Jonas Lochmann
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { NewPermissionStatus } from '../model/newpermissionstatus'
|
||||
import { ProtectionLevel } from '../model/protectionlevel'
|
||||
import { RuntimePermissionStatus } from '../model/runtimepermissionstatus'
|
||||
import { AppLogicAction } from './basetypes'
|
||||
|
||||
export class UpdateDeviceStatusAction extends AppLogicAction {
|
||||
readonly newProtetionLevel?: ProtectionLevel
|
||||
readonly newUsageStatsPermissionStatus?: RuntimePermissionStatus
|
||||
readonly newNotificationAccessPermission?: NewPermissionStatus
|
||||
readonly newAppVersion?: number
|
||||
readonly didReboot: boolean
|
||||
|
||||
constructor ({ newProtetionLevel, newUsageStatsPermissionStatus, newNotificationAccessPermission, newAppVersion, didReboot }: {
|
||||
newProtetionLevel?: ProtectionLevel
|
||||
newUsageStatsPermissionStatus?: RuntimePermissionStatus
|
||||
newNotificationAccessPermission?: NewPermissionStatus
|
||||
newAppVersion?: number
|
||||
didReboot: boolean
|
||||
}) {
|
||||
super()
|
||||
|
||||
if (newAppVersion !== undefined) {
|
||||
if (!Number.isSafeInteger(newAppVersion) || (newAppVersion < 0)) {
|
||||
throw new Error('invalid new ap version')
|
||||
}
|
||||
}
|
||||
|
||||
this.newProtetionLevel = newProtetionLevel
|
||||
this.newUsageStatsPermissionStatus = newUsageStatsPermissionStatus
|
||||
this.newNotificationAccessPermission = newNotificationAccessPermission
|
||||
this.newAppVersion = newAppVersion
|
||||
this.didReboot = didReboot
|
||||
}
|
||||
|
||||
serialize = (): SerializedUpdateDeviceStatusAction => ({
|
||||
type: 'UPDATE_DEVICE_STATUS',
|
||||
protectionLevel: this.newProtetionLevel,
|
||||
usageStats: this.newUsageStatsPermissionStatus,
|
||||
notificationAccess: this.newNotificationAccessPermission,
|
||||
appVersion: this.newAppVersion,
|
||||
didReboot: this.didReboot
|
||||
})
|
||||
|
||||
static parse = ({ protectionLevel, usageStats, notificationAccess, appVersion, didReboot }: SerializedUpdateDeviceStatusAction) => (
|
||||
new UpdateDeviceStatusAction({
|
||||
newProtetionLevel: protectionLevel,
|
||||
newUsageStatsPermissionStatus: usageStats,
|
||||
newNotificationAccessPermission: notificationAccess,
|
||||
newAppVersion: appVersion,
|
||||
didReboot: !!didReboot
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
export interface SerializedUpdateDeviceStatusAction {
|
||||
type: 'UPDATE_DEVICE_STATUS'
|
||||
protectionLevel?: ProtectionLevel
|
||||
usageStats?: RuntimePermissionStatus
|
||||
notificationAccess?: NewPermissionStatus
|
||||
appVersion?: number
|
||||
didReboot?: boolean
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* server component for the TimeLimit App
|
||||
* Copyright (C) 2019 Jonas Lochmann
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { assertIdWithinFamily } from '../util/token'
|
||||
import { ParentAction } from './basetypes'
|
||||
|
||||
export class UpdateNetworkTimeVerificationAction extends ParentAction {
|
||||
readonly deviceId: string
|
||||
readonly mode: 'disabled' | 'if possible' | 'enabled'
|
||||
|
||||
constructor ({ deviceId, mode }: {
|
||||
deviceId: string
|
||||
mode: 'disabled' | 'if possible' | 'enabled'
|
||||
}) {
|
||||
super()
|
||||
|
||||
assertIdWithinFamily(deviceId)
|
||||
|
||||
this.deviceId = deviceId
|
||||
this.mode = mode
|
||||
}
|
||||
|
||||
serialize = (): SerialiizedUpdateNetworkTimeVerificationAction => ({
|
||||
type: 'UPDATE_NETWORK_TIME_VERIFICATION',
|
||||
deviceId: this.deviceId,
|
||||
mode: this.mode
|
||||
})
|
||||
|
||||
static parse = ({ deviceId, mode }: SerialiizedUpdateNetworkTimeVerificationAction) => (
|
||||
new UpdateNetworkTimeVerificationAction({
|
||||
deviceId,
|
||||
mode
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
export interface SerialiizedUpdateNetworkTimeVerificationAction {
|
||||
type: 'UPDATE_NETWORK_TIME_VERIFICATION'
|
||||
deviceId: string
|
||||
mode: 'disabled' | 'if possible' | 'enabled'
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* server component for the TimeLimit App
|
||||
* Copyright (C) 2019 Jonas Lochmann
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { assertIdWithinFamily } from '../util/token'
|
||||
import { ParentAction } from './basetypes'
|
||||
|
||||
export class UpdateParentNotificationFlagsAction extends ParentAction {
|
||||
readonly parentId: string
|
||||
readonly flags: number
|
||||
readonly set: boolean
|
||||
|
||||
constructor ({ parentId, flags, set }: {
|
||||
parentId: string
|
||||
flags: number
|
||||
set: boolean
|
||||
}) {
|
||||
super()
|
||||
|
||||
assertIdWithinFamily(parentId)
|
||||
|
||||
if (!Number.isSafeInteger(flags)) {
|
||||
throw new Error('flags must be an integer')
|
||||
}
|
||||
|
||||
if (flags < 0 || flags > 1) {
|
||||
throw new Error('flags are out of the valid range')
|
||||
}
|
||||
|
||||
this.parentId = parentId
|
||||
this.flags = flags
|
||||
this.set = set
|
||||
}
|
||||
|
||||
serialize = (): SerializedUpdateParentNotificationFlagsAction => ({
|
||||
type: 'UPDATE_PARENT_NOTIFICATION_FLAGS',
|
||||
parentId: this.parentId,
|
||||
flags: this.flags,
|
||||
set: this.set
|
||||
})
|
||||
|
||||
static parse = ({ parentId, flags, set }: SerializedUpdateParentNotificationFlagsAction) => (
|
||||
new UpdateParentNotificationFlagsAction({ parentId, flags, set })
|
||||
)
|
||||
}
|
||||
|
||||
export interface SerializedUpdateParentNotificationFlagsAction {
|
||||
type: 'UPDATE_PARENT_NOTIFICATION_FLAGS'
|
||||
parentId: string
|
||||
flags: number
|
||||
set: boolean
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
* server component for the TimeLimit App
|
||||
* Copyright (C) 2019 Jonas Lochmann
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { assertIdWithinFamily } from '../util/token'
|
||||
import { ParentAction } from './basetypes'
|
||||
|
||||
export class UpdateTimelimitRuleAction extends ParentAction {
|
||||
readonly ruleId: string
|
||||
readonly maximumTimeInMillis: number
|
||||
readonly dayMask: number
|
||||
readonly applyToExtraTimeUsage: boolean
|
||||
|
||||
constructor ({ ruleId, maximumTimeInMillis, dayMask, applyToExtraTimeUsage }: {
|
||||
ruleId: string
|
||||
maximumTimeInMillis: number
|
||||
dayMask: number
|
||||
applyToExtraTimeUsage: boolean
|
||||
}) {
|
||||
super()
|
||||
|
||||
this.ruleId = ruleId
|
||||
this.maximumTimeInMillis = maximumTimeInMillis
|
||||
this.dayMask = dayMask
|
||||
this.applyToExtraTimeUsage = applyToExtraTimeUsage
|
||||
|
||||
assertIdWithinFamily(ruleId)
|
||||
|
||||
if (maximumTimeInMillis < 0 || (!Number.isSafeInteger(maximumTimeInMillis))) {
|
||||
throw new Error('maximumTimeInMillis must be >= 0')
|
||||
}
|
||||
|
||||
if (!(
|
||||
Number.isSafeInteger(dayMask) ||
|
||||
dayMask < 0 ||
|
||||
dayMask > (1 | 2 | 4 | 8 | 16 | 32 | 64)
|
||||
)) {
|
||||
throw new Error('invalid day mask')
|
||||
}
|
||||
}
|
||||
|
||||
serialize = (): SerializedUpdateTimelimitRuleAction => ({
|
||||
type: 'UPDATE_TIMELIMIT_RULE',
|
||||
ruleId: this.ruleId,
|
||||
time: this.maximumTimeInMillis,
|
||||
days: this.dayMask,
|
||||
extraTime: this.applyToExtraTimeUsage
|
||||
})
|
||||
|
||||
static parse = ({ ruleId, time, days, extraTime }: SerializedUpdateTimelimitRuleAction) => (
|
||||
new UpdateTimelimitRuleAction({
|
||||
ruleId,
|
||||
maximumTimeInMillis: time,
|
||||
dayMask: days,
|
||||
applyToExtraTimeUsage: extraTime
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
export interface SerializedUpdateTimelimitRuleAction {
|
||||
type: 'UPDATE_TIMELIMIT_RULE'
|
||||
ruleId: string
|
||||
time: number
|
||||
days: number
|
||||
extraTime: boolean
|
||||
}
|
||||
+130
@@ -0,0 +1,130 @@
|
||||
/*
|
||||
* server component for the TimeLimit App
|
||||
* Copyright (C) 2019 Jonas Lochmann
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { json } from 'body-parser'
|
||||
import { Router } from 'express'
|
||||
import { OAuth2Client } from 'google-auth-library'
|
||||
import { BadRequest } from 'http-errors'
|
||||
import { Database } from '../database'
|
||||
import { createAuthTokenByMailAddress } from '../function/authentication'
|
||||
import { sendLoginCode, signInByMailCode } from '../function/authentication/login-by-mail'
|
||||
import {
|
||||
isSendMailLoginCodeRequest,
|
||||
isSignInByMailCodeRequest,
|
||||
isSignInWithGoogleRequest
|
||||
} from './validator'
|
||||
|
||||
const CLIENT_ID = process.env.GOOGLE_SIGN_IN_CLIENT_ID || ''
|
||||
const client = new OAuth2Client(CLIENT_ID)
|
||||
|
||||
const getMailByGoogleAuthToken = async (idToken: string) => {
|
||||
const ticket = await client.verifyIdToken({
|
||||
idToken,
|
||||
audience: CLIENT_ID
|
||||
})
|
||||
|
||||
if (!ticket) {
|
||||
throw new BadRequest()
|
||||
}
|
||||
|
||||
const payload = ticket.getPayload()
|
||||
|
||||
if (!payload) {
|
||||
throw new BadRequest()
|
||||
}
|
||||
|
||||
if (!payload.email_verified) {
|
||||
throw new BadRequest()
|
||||
}
|
||||
|
||||
const mail = payload.email
|
||||
|
||||
if (!mail) {
|
||||
throw new BadRequest()
|
||||
}
|
||||
|
||||
if (!(
|
||||
mail.endsWith('@gmail.com') ||
|
||||
mail.endsWith('@googlemail.com')
|
||||
)) {
|
||||
throw new BadRequest()
|
||||
}
|
||||
|
||||
return mail
|
||||
}
|
||||
|
||||
export const createAuthRouter = (database: Database) => {
|
||||
const router = Router()
|
||||
|
||||
router.post('/sign-in-with-google', json(), async (req, res, next) => {
|
||||
try {
|
||||
if (!isSignInWithGoogleRequest(req.body)) {
|
||||
res.sendStatus(400)
|
||||
return
|
||||
}
|
||||
|
||||
const { googleAuthToken } = req.body
|
||||
|
||||
const mail = await getMailByGoogleAuthToken(googleAuthToken)
|
||||
const mailAuthToken = await createAuthTokenByMailAddress({ mail, database })
|
||||
|
||||
res.json({
|
||||
mailAuthToken
|
||||
})
|
||||
} catch (ex) {
|
||||
next(ex)
|
||||
}
|
||||
})
|
||||
|
||||
router.post('/send-mail-login-code', json(), async (req, res, next) => {
|
||||
try {
|
||||
if (!isSendMailLoginCodeRequest(req.body)) {
|
||||
throw new BadRequest()
|
||||
}
|
||||
|
||||
const { mailLoginToken } = await sendLoginCode({
|
||||
mail: req.body.mail,
|
||||
locale: req.body.locale,
|
||||
database
|
||||
})
|
||||
|
||||
res.json({ mailLoginToken })
|
||||
} catch (ex) {
|
||||
next(ex)
|
||||
}
|
||||
})
|
||||
|
||||
router.post('/sign-in-by-mail-code', json(), async (req, res, next) => {
|
||||
try {
|
||||
if (!isSignInByMailCodeRequest(req.body)) {
|
||||
throw new BadRequest()
|
||||
}
|
||||
|
||||
const { mailAuthToken } = await signInByMailCode({
|
||||
receivedCode: req.body.receivedCode,
|
||||
mailLoginToken: req.body.mailLoginToken,
|
||||
database
|
||||
})
|
||||
|
||||
res.json({ mailAuthToken })
|
||||
} catch (ex) {
|
||||
next(ex)
|
||||
}
|
||||
})
|
||||
|
||||
return router
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
/*
|
||||
* server component for the TimeLimit App
|
||||
* Copyright (C) 2019 Jonas Lochmann
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { json } from 'body-parser'
|
||||
import { Router } from 'express'
|
||||
import { BadRequest } from 'http-errors'
|
||||
import { Database } from '../database'
|
||||
import { addChildDevice } from '../function/child/add-device'
|
||||
import { logoutAtPrimaryDevice } from '../function/child/logout-at-primary-device'
|
||||
import { setPrimaryDevice } from '../function/child/set-primary-device'
|
||||
import { WebsocketApi } from '../websocket'
|
||||
import { isRegisterChildDeviceRequest, isRequestWithAuthToken, isUpdatePrimaryDeviceRequest } from './validator'
|
||||
|
||||
export const createChildRouter = ({ database, websocket }: {
|
||||
database: Database,
|
||||
websocket: WebsocketApi
|
||||
}) => {
|
||||
const router = Router()
|
||||
|
||||
router.post('/add-device', json(), async (req, res, next) => {
|
||||
try {
|
||||
if (!isRegisterChildDeviceRequest(req.body)) {
|
||||
throw new BadRequest()
|
||||
}
|
||||
|
||||
const { deviceAuthToken, deviceId } = await addChildDevice({
|
||||
request: req.body,
|
||||
database,
|
||||
websocket
|
||||
})
|
||||
|
||||
res.json({
|
||||
deviceAuthToken,
|
||||
ownDeviceId: deviceId
|
||||
})
|
||||
} catch (ex) {
|
||||
next(ex)
|
||||
}
|
||||
})
|
||||
|
||||
router.post('/update-primary-device', json(), async (req, res, next) => {
|
||||
try {
|
||||
if (!isUpdatePrimaryDeviceRequest(req.body)) {
|
||||
throw new BadRequest()
|
||||
}
|
||||
|
||||
const response = await setPrimaryDevice({
|
||||
database,
|
||||
deviceAuthToken: req.body.authToken,
|
||||
currentUserId: req.body.currentUserId,
|
||||
websocket,
|
||||
action: req.body.action
|
||||
})
|
||||
|
||||
res.json({
|
||||
status: response
|
||||
})
|
||||
} catch (ex) {
|
||||
next(ex)
|
||||
}
|
||||
})
|
||||
|
||||
router.post('/logout-at-primary-device', json(), async (req, res, next) => {
|
||||
try {
|
||||
if (!isRequestWithAuthToken(req.body)) {
|
||||
throw new BadRequest()
|
||||
}
|
||||
|
||||
await logoutAtPrimaryDevice({
|
||||
deviceAuthToken: req.body.deviceAuthToken,
|
||||
database,
|
||||
websocket
|
||||
})
|
||||
|
||||
res.json({
|
||||
ok: true
|
||||
})
|
||||
} catch (ex) {
|
||||
next(ex)
|
||||
}
|
||||
})
|
||||
|
||||
return router
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* server component for the TimeLimit App
|
||||
* Copyright (C) 2019 Jonas Lochmann
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import * as express from 'express'
|
||||
import { VisibleConnectedDevicesManager } from '../connected-devices'
|
||||
import { Database } from '../database'
|
||||
import { WebsocketApi } from '../websocket'
|
||||
import { createAuthRouter } from './auth'
|
||||
import { createChildRouter } from './child'
|
||||
import { createParentRouter } from './parent'
|
||||
import { createPurchaseRouter } from './purchase'
|
||||
import { createSyncRouter } from './sync'
|
||||
|
||||
export const createApi = ({ database, websocket, connectedDevicesManager }: {
|
||||
database: Database
|
||||
websocket: WebsocketApi
|
||||
connectedDevicesManager: VisibleConnectedDevicesManager
|
||||
}) => {
|
||||
const app = express()
|
||||
|
||||
app.disable('x-powered-by')
|
||||
|
||||
app.get('/time', (req, res) => {
|
||||
res.json({
|
||||
ms: Date.now()
|
||||
})
|
||||
})
|
||||
|
||||
app.use('/auth', createAuthRouter(database))
|
||||
app.use('/child', createChildRouter({ database, websocket }))
|
||||
app.use('/parent', createParentRouter({ database, websocket }))
|
||||
app.use('/purchase', createPurchaseRouter({ database, websocket }))
|
||||
app.use('/sync', createSyncRouter({ database, websocket, connectedDevicesManager }))
|
||||
|
||||
return app
|
||||
}
|
||||
@@ -0,0 +1,257 @@
|
||||
/*
|
||||
* server component for the TimeLimit App
|
||||
* Copyright (C) 2019 Jonas Lochmann
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { json } from 'body-parser'
|
||||
import { Router } from 'express'
|
||||
import { BadRequest, Unauthorized } from 'http-errors'
|
||||
import { Database } from '../database'
|
||||
import { removeDevice } from '../function/device/remove-device'
|
||||
import { canRecoverPassword } from '../function/parent/can-recover-password'
|
||||
import { createAddDeviceToken } from '../function/parent/create-add-device-token'
|
||||
import { createFamily } from '../function/parent/create-family'
|
||||
import { getStatusByMailToken } from '../function/parent/get-status-by-mail-address'
|
||||
import { linkMailAddress } from '../function/parent/link-mail-address'
|
||||
import { recoverParentPassword } from '../function/parent/recover-parent-password'
|
||||
import { signInIntoFamily } from '../function/parent/sign-in-into-family'
|
||||
import { WebsocketApi } from '../websocket'
|
||||
import {
|
||||
isCanRecoverPasswordRequest, isCreateFamilyByMailTokenRequest,
|
||||
isCreateRegisterDeviceTokenRequest, isLinkParentMailAddressRequest,
|
||||
isMailAuthTokenRequestBody, isRecoverParentPasswordRequest,
|
||||
isRemoveDeviceRequest, isSignIntoFamilyRequest
|
||||
} from './validator'
|
||||
|
||||
export const createParentRouter = ({ database, websocket }: {database: Database, websocket: WebsocketApi}) => {
|
||||
const router = Router()
|
||||
|
||||
router.post('/get-status-by-mail-address', json(), async (req, res, next) => {
|
||||
try {
|
||||
if (!isMailAuthTokenRequestBody(req.body)) {
|
||||
throw new BadRequest()
|
||||
}
|
||||
|
||||
const { mailAuthToken } = req.body
|
||||
const { status, mail } = await getStatusByMailToken({ database, mailAuthToken })
|
||||
|
||||
res.json({ status, mail })
|
||||
} catch (ex) {
|
||||
next(ex)
|
||||
}
|
||||
})
|
||||
|
||||
router.post('/create-family', json(), async (req, res, next) => {
|
||||
try {
|
||||
if (!isCreateFamilyByMailTokenRequest(req.body)) {
|
||||
throw new BadRequest()
|
||||
}
|
||||
|
||||
const result = await createFamily({
|
||||
database,
|
||||
firstParentDevice: req.body.parentDevice,
|
||||
mailAuthToken: req.body.mailAuthToken,
|
||||
password: req.body.parentPassword,
|
||||
deviceName: req.body.deviceName,
|
||||
parentName: req.body.parentName,
|
||||
timeZone: req.body.timeZone
|
||||
})
|
||||
|
||||
res.json({
|
||||
deviceAuthToken: result.deviceAuthToken,
|
||||
ownDeviceId: result.deviceId
|
||||
})
|
||||
} catch (ex) {
|
||||
next(ex)
|
||||
}
|
||||
})
|
||||
|
||||
router.post('/sign-in-into-family', json(), async (req, res, next) => {
|
||||
try {
|
||||
if (!isSignIntoFamilyRequest(req.body)) {
|
||||
throw new BadRequest()
|
||||
}
|
||||
|
||||
const result = await signInIntoFamily({
|
||||
database,
|
||||
newDeviceInfo: req.body.parentDevice,
|
||||
mailAuthToken: req.body.mailAuthToken,
|
||||
deviceName: req.body.deviceName,
|
||||
websocket
|
||||
})
|
||||
|
||||
res.json({
|
||||
deviceAuthToken: result.deviceAuthToken,
|
||||
ownDeviceId: result.deviceId
|
||||
})
|
||||
} catch (ex) {
|
||||
next(ex)
|
||||
}
|
||||
})
|
||||
|
||||
router.post('/can-recover-password', json(), async (req, res, next) => {
|
||||
try {
|
||||
if (!isCanRecoverPasswordRequest(req.body)) {
|
||||
throw new BadRequest()
|
||||
}
|
||||
|
||||
const canRecover = await canRecoverPassword({
|
||||
database,
|
||||
parentUserId: req.body.parentUserId,
|
||||
mailAuthToken: req.body.mailAuthToken
|
||||
})
|
||||
|
||||
res.json({ canRecover })
|
||||
} catch (ex) {
|
||||
next(ex)
|
||||
}
|
||||
})
|
||||
|
||||
router.post('/recover-parent-password', json(), async (req, res, next) => {
|
||||
try {
|
||||
if (!isRecoverParentPasswordRequest(req.body)) {
|
||||
throw new BadRequest()
|
||||
}
|
||||
|
||||
await recoverParentPassword({
|
||||
database,
|
||||
websocket,
|
||||
password: req.body.password,
|
||||
mailAuthToken: req.body.mailAuthToken
|
||||
})
|
||||
|
||||
res.json({ ok: true })
|
||||
} catch (ex) {
|
||||
next(ex)
|
||||
}
|
||||
})
|
||||
|
||||
async function assertAuthValidAndReturnDeviceEntry ({ deviceAuthToken, parentId, secondPasswordHash }: {
|
||||
deviceAuthToken: string
|
||||
parentId: string
|
||||
secondPasswordHash: string
|
||||
}) {
|
||||
const deviceEntry = await database.device.findOne({
|
||||
where: {
|
||||
deviceAuthToken: deviceAuthToken
|
||||
}
|
||||
})
|
||||
|
||||
if (!deviceEntry) {
|
||||
throw new Unauthorized()
|
||||
}
|
||||
|
||||
if (secondPasswordHash === 'device') {
|
||||
if (!deviceEntry.isUserKeptSignedIn) {
|
||||
throw new Unauthorized()
|
||||
}
|
||||
|
||||
const parentEntry = await database.user.findOne({
|
||||
where: {
|
||||
familyId: deviceEntry.familyId,
|
||||
type: 'parent',
|
||||
userId: deviceEntry.currentUserId
|
||||
}
|
||||
})
|
||||
|
||||
if (!parentEntry) {
|
||||
throw new Unauthorized()
|
||||
}
|
||||
} else {
|
||||
const parentEntry = await database.user.findOne({
|
||||
where: {
|
||||
familyId: deviceEntry.familyId,
|
||||
type: 'parent',
|
||||
userId: parentId,
|
||||
secondPasswordHash: secondPasswordHash
|
||||
}
|
||||
})
|
||||
|
||||
if (!parentEntry) {
|
||||
throw new Unauthorized()
|
||||
}
|
||||
}
|
||||
|
||||
return deviceEntry
|
||||
}
|
||||
|
||||
router.post('/create-add-device-token', json(), async (req, res, next) => {
|
||||
try {
|
||||
if (!isCreateRegisterDeviceTokenRequest(req.body)) {
|
||||
throw new BadRequest()
|
||||
}
|
||||
|
||||
const deviceEntry = await assertAuthValidAndReturnDeviceEntry({
|
||||
deviceAuthToken: req.body.deviceAuthToken,
|
||||
parentId: req.body.parentId,
|
||||
secondPasswordHash: req.body.parentPasswordSecondHash
|
||||
})
|
||||
|
||||
const { token, deviceId } = await createAddDeviceToken({ familyId: deviceEntry.familyId, database })
|
||||
|
||||
res.json({ token, deviceId })
|
||||
} catch (ex) {
|
||||
next(ex)
|
||||
}
|
||||
})
|
||||
|
||||
router.post('/link-mail-address', json(), async (req, res, next) => {
|
||||
try {
|
||||
if (!isLinkParentMailAddressRequest(req.body)) {
|
||||
throw new BadRequest()
|
||||
}
|
||||
|
||||
await linkMailAddress({
|
||||
mailAuthToken: req.body.mailAuthToken,
|
||||
deviceAuthToken: req.body.deviceAuthToken,
|
||||
parentPasswordSecondHash: req.body.parentPasswordSecondHash,
|
||||
parentUserId: req.body.parentUserId,
|
||||
websocket,
|
||||
database
|
||||
})
|
||||
|
||||
res.json({ ok: true })
|
||||
} catch (ex) {
|
||||
next(ex)
|
||||
}
|
||||
})
|
||||
|
||||
router.post('/remove-device', json(), async (req, res, next) => {
|
||||
try {
|
||||
if (!isRemoveDeviceRequest(req.body)) {
|
||||
throw new BadRequest()
|
||||
}
|
||||
|
||||
const deviceEntry = await assertAuthValidAndReturnDeviceEntry({
|
||||
deviceAuthToken: req.body.deviceAuthToken,
|
||||
parentId: req.body.parentUserId,
|
||||
secondPasswordHash: req.body.parentPasswordSecondHash
|
||||
})
|
||||
|
||||
await removeDevice({
|
||||
database,
|
||||
familyId: deviceEntry.familyId,
|
||||
deviceId: req.body.deviceId,
|
||||
websocket
|
||||
})
|
||||
|
||||
res.json({ ok: true })
|
||||
} catch (ex) {
|
||||
next(ex)
|
||||
}
|
||||
})
|
||||
|
||||
return router
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
/*
|
||||
* server component for the TimeLimit App
|
||||
* Copyright (C) 2019 Jonas Lochmann
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { json } from 'body-parser'
|
||||
import { Router } from 'express'
|
||||
import { BadRequest, Conflict, Unauthorized } from 'http-errors'
|
||||
import { Database } from '../database'
|
||||
import {
|
||||
addPurchase,
|
||||
areGooglePlayPaymentsPossible,
|
||||
canDoNextPurchase,
|
||||
isGooglePlayPurchaseSignatureValid,
|
||||
requireFamilyEntry
|
||||
} from '../function/purchase'
|
||||
import { WebsocketApi } from '../websocket'
|
||||
import { isCanDoPurchaseRequest, isFinishPurchaseByGooglePlayRequest } from './validator'
|
||||
|
||||
export const createPurchaseRouter = ({ database, websocket }: {
|
||||
database: Database
|
||||
websocket: WebsocketApi
|
||||
}) => {
|
||||
const router = Router()
|
||||
|
||||
router.post('/can-do-purchase', json(), async (req, res, next) => {
|
||||
if (!areGooglePlayPaymentsPossible) {
|
||||
res.json({ canDoPurchase: 'no because not supported by the server' })
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
if (!isCanDoPurchaseRequest(req.body)) {
|
||||
throw new BadRequest()
|
||||
}
|
||||
|
||||
const familyEntry = await requireFamilyEntry({
|
||||
database,
|
||||
deviceAuthToken: req.body.deviceAuthToken
|
||||
})
|
||||
|
||||
const result = canDoNextPurchase({ fullVersionUntil: parseInt(familyEntry.fullVersionUntil, 10) })
|
||||
|
||||
res.json({
|
||||
canDoPurchase: result ? 'yes' : 'no due to old purchase'
|
||||
})
|
||||
} catch (ex) {
|
||||
next(ex)
|
||||
}
|
||||
})
|
||||
|
||||
router.post('/finish-purchase-by-google-play', json(), async (req, res, next) => {
|
||||
try {
|
||||
if (!isFinishPurchaseByGooglePlayRequest(req.body)) {
|
||||
throw new BadRequest()
|
||||
}
|
||||
|
||||
const deviceEntryUnsafe = await database.device.findOne({
|
||||
where: {
|
||||
deviceAuthToken: req.body.deviceAuthToken
|
||||
},
|
||||
attributes: ['familyId']
|
||||
})
|
||||
|
||||
if (!deviceEntryUnsafe) {
|
||||
throw new Unauthorized()
|
||||
}
|
||||
|
||||
const deviceEntry = {
|
||||
familyId: deviceEntryUnsafe.familyId
|
||||
}
|
||||
|
||||
if (!isGooglePlayPurchaseSignatureValid({
|
||||
receipt: req.body.receipt,
|
||||
signature: req.body.signature
|
||||
})) {
|
||||
throw new Conflict()
|
||||
}
|
||||
|
||||
const receipt = JSON.parse(req.body.receipt)
|
||||
|
||||
if (typeof receipt !== 'object') {
|
||||
throw new Conflict()
|
||||
}
|
||||
|
||||
let type: 'month' | 'year'
|
||||
|
||||
if (receipt.productId === 'premium_year_2018') {
|
||||
type = 'year'
|
||||
} else if (receipt.productId === 'premium_month_2018') {
|
||||
type = 'month'
|
||||
} else {
|
||||
throw new Conflict()
|
||||
}
|
||||
|
||||
const orderId = receipt.orderId
|
||||
|
||||
if (typeof orderId !== 'string') {
|
||||
throw new Conflict()
|
||||
}
|
||||
|
||||
await addPurchase({
|
||||
database,
|
||||
familyId: deviceEntry.familyId,
|
||||
type,
|
||||
transactionId: orderId,
|
||||
websocket
|
||||
})
|
||||
|
||||
res.json({ ok: true })
|
||||
} catch (ex) {
|
||||
next(ex)
|
||||
}
|
||||
})
|
||||
|
||||
return router
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
/*
|
||||
* server component for the TimeLimit App
|
||||
* Copyright (C) 2019 Jonas Lochmann
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { ClientDataStatus } from '../object/clientdatastatus'
|
||||
import { optionalPasswordRegex, optionalSaltRegex } from '../util/password'
|
||||
|
||||
export interface ClientPushChangesRequest {
|
||||
deviceAuthToken: string
|
||||
actions: Array<{
|
||||
encodedAction: string
|
||||
sequenceNumber: number
|
||||
integrity: string
|
||||
type: 'appLogic' | 'parent' | 'child'
|
||||
userId: string
|
||||
}>
|
||||
}
|
||||
|
||||
export interface ClientPullChangesRequest {
|
||||
deviceAuthToken: string
|
||||
status: ClientDataStatus
|
||||
}
|
||||
|
||||
export interface SignInWithGoogleRequest {
|
||||
googleAuthToken: string
|
||||
}
|
||||
|
||||
export interface MailAuthTokenRequestBody {
|
||||
mailAuthToken: string
|
||||
}
|
||||
|
||||
export interface NewDeviceInfo {
|
||||
model: string
|
||||
}
|
||||
|
||||
export interface ParentPassword {
|
||||
hash: string
|
||||
secondHash: string
|
||||
secondSalt: string
|
||||
}
|
||||
|
||||
export const assertParentPasswordValid = (password: ParentPassword) => {
|
||||
if (password.hash === '' || password.secondHash === '' || password.secondSalt === '') {
|
||||
throw new Error('missing fields at parent password')
|
||||
}
|
||||
|
||||
if (!(optionalPasswordRegex.test(password.hash) && optionalPasswordRegex.test(password.secondHash) && optionalSaltRegex.test(password.secondSalt))) {
|
||||
throw new Error('invalid parent password')
|
||||
}
|
||||
}
|
||||
|
||||
export interface CreateFamilyByMailTokenRequest {
|
||||
mailAuthToken: string
|
||||
parentPassword: ParentPassword
|
||||
parentDevice: NewDeviceInfo
|
||||
deviceName: string
|
||||
timeZone: string
|
||||
parentName: string
|
||||
}
|
||||
|
||||
export interface SignIntoFamilyRequest {
|
||||
mailAuthToken: string
|
||||
parentDevice: NewDeviceInfo
|
||||
deviceName: string
|
||||
}
|
||||
|
||||
export interface RecoverParentPasswordRequest {
|
||||
mailAuthToken: string
|
||||
password: ParentPassword
|
||||
}
|
||||
|
||||
export interface CanRecoverPasswordRequest {
|
||||
mailAuthToken: string
|
||||
parentUserId: string
|
||||
}
|
||||
|
||||
export interface RegisterChildDeviceRequest {
|
||||
registerToken: string
|
||||
childDevice: NewDeviceInfo
|
||||
deviceName: string
|
||||
}
|
||||
|
||||
export interface CreateRegisterDeviceTokenRequest {
|
||||
deviceAuthToken: string
|
||||
parentId: string
|
||||
parentPasswordSecondHash: string
|
||||
}
|
||||
|
||||
export interface CanDoPurchaseRequest {
|
||||
type: 'googleplay' | 'any'
|
||||
deviceAuthToken: string
|
||||
}
|
||||
|
||||
export interface FinishPurchaseByGooglePlayRequest {
|
||||
deviceAuthToken: string
|
||||
receipt: string
|
||||
signature: string
|
||||
}
|
||||
|
||||
export interface LinkParentMailAddressRequest {
|
||||
mailAuthToken: string
|
||||
deviceAuthToken: string
|
||||
parentUserId: string
|
||||
parentPasswordSecondHash: string
|
||||
}
|
||||
|
||||
export interface UpdatePrimaryDeviceRequest {
|
||||
action: 'set this device' | 'unset this device'
|
||||
currentUserId: string
|
||||
authToken: string
|
||||
}
|
||||
|
||||
export interface RemoveDeviceRequest {
|
||||
deviceAuthToken: string
|
||||
parentUserId: string
|
||||
parentPasswordSecondHash: string
|
||||
deviceId: string
|
||||
}
|
||||
|
||||
export interface RequestWithAuthToken {
|
||||
deviceAuthToken: string
|
||||
}
|
||||
|
||||
export interface SendMailLoginCodeRequest {
|
||||
mail: string
|
||||
locale: string
|
||||
}
|
||||
|
||||
export interface SignInByMailCodeRequest {
|
||||
mailLoginToken: string
|
||||
receivedCode: string
|
||||
}
|
||||
|
||||
export { SerializedParentAction, SerializedChildAction, SerializedAppLogicAction } from '../action/serialization'
|
||||
+151
@@ -0,0 +1,151 @@
|
||||
/*
|
||||
* server component for the TimeLimit App
|
||||
* Copyright (C) 2019 Jonas Lochmann
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { json } from 'body-parser'
|
||||
import { Router } from 'express'
|
||||
import { BadRequest, Unauthorized } from 'http-errors'
|
||||
import { VisibleConnectedDevicesManager } from '../connected-devices'
|
||||
import { Database } from '../database'
|
||||
import { reportDeviceRemoved } from '../function/device/report-device-removed'
|
||||
import { applyActionsFromDevice } from '../function/sync/apply-actions'
|
||||
import { generateServerDataStatus } from '../function/sync/get-server-data-status'
|
||||
import { WebsocketApi } from '../websocket'
|
||||
import { isClientPullChangesRequest, isClientPushChangesRequest, isRequestWithAuthToken } from './validator'
|
||||
|
||||
const getRoundedTimestampForLastConnectivity = () => {
|
||||
const now = Date.now()
|
||||
|
||||
return now - (now % (1000 * 60 * 60 * 12 /* 12 hours */))
|
||||
}
|
||||
|
||||
export const createSyncRouter = ({ database, websocket, connectedDevicesManager }: {
|
||||
database: Database
|
||||
websocket: WebsocketApi
|
||||
connectedDevicesManager: VisibleConnectedDevicesManager
|
||||
}) => {
|
||||
const router = Router()
|
||||
|
||||
router.post('/push-actions', json(), async (req, res, next) => {
|
||||
try {
|
||||
if (!isClientPushChangesRequest(req.body)) {
|
||||
throw new BadRequest()
|
||||
}
|
||||
|
||||
const { shouldDoFullSync } = await applyActionsFromDevice({
|
||||
request: req.body,
|
||||
database,
|
||||
websocket,
|
||||
connectedDevicesManager
|
||||
})
|
||||
|
||||
res.json({
|
||||
shouldDoFullSync
|
||||
})
|
||||
} catch (ex) {
|
||||
next(ex)
|
||||
}
|
||||
})
|
||||
|
||||
router.post('/pull-status', json(), async (req, res, next) => {
|
||||
try {
|
||||
const { body } = req
|
||||
|
||||
if (!isClientPullChangesRequest(body)) {
|
||||
throw new BadRequest()
|
||||
}
|
||||
|
||||
await database.transaction(async (transaction) => {
|
||||
const deviceEntryUnsafe = await database.device.findOne({
|
||||
where: {
|
||||
deviceAuthToken: body.deviceAuthToken
|
||||
},
|
||||
attributes: ['familyId', 'lastConnectivity'],
|
||||
transaction
|
||||
})
|
||||
|
||||
if (!deviceEntryUnsafe) {
|
||||
throw new Unauthorized()
|
||||
}
|
||||
|
||||
const { familyId, lastConnectivity } = deviceEntryUnsafe
|
||||
const now = getRoundedTimestampForLastConnectivity()
|
||||
|
||||
if (parseInt(lastConnectivity, 10) !== now) {
|
||||
await database.device.update({
|
||||
lastConnectivity: now.toString(10)
|
||||
}, {
|
||||
where: {
|
||||
deviceAuthToken: body.deviceAuthToken
|
||||
},
|
||||
transaction
|
||||
})
|
||||
}
|
||||
|
||||
const serverStatus = await generateServerDataStatus({
|
||||
database,
|
||||
familyId,
|
||||
clientStatus: body.status,
|
||||
transaction
|
||||
})
|
||||
|
||||
res.json(serverStatus)
|
||||
})
|
||||
} catch (ex) {
|
||||
next(ex)
|
||||
}
|
||||
})
|
||||
|
||||
router.post('/report-removed', json(), async (req, res, next) => {
|
||||
try {
|
||||
if (!isRequestWithAuthToken(req.body)) {
|
||||
throw new BadRequest()
|
||||
}
|
||||
|
||||
await reportDeviceRemoved({
|
||||
database,
|
||||
deviceAuthToken: req.body.deviceAuthToken,
|
||||
websocket
|
||||
})
|
||||
|
||||
res.json({ ok: true })
|
||||
} catch (ex) {
|
||||
next(ex)
|
||||
}
|
||||
})
|
||||
|
||||
router.post('/is-device-removed', json(), async (req, res, next) => {
|
||||
try {
|
||||
if (!isRequestWithAuthToken(req.body)) {
|
||||
throw new BadRequest()
|
||||
}
|
||||
|
||||
const removedEntry = await database.oldDevice.findOne({
|
||||
where: {
|
||||
deviceAuthToken: req.body.deviceAuthToken
|
||||
}
|
||||
})
|
||||
|
||||
res.json({
|
||||
isDeviceRemoved: !!removedEntry
|
||||
})
|
||||
} catch (ex) {
|
||||
next(ex)
|
||||
}
|
||||
})
|
||||
|
||||
return router
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,214 @@
|
||||
/*
|
||||
* server component for the TimeLimit App
|
||||
* Copyright (C) 2019 Jonas Lochmann
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { EventEmitter } from 'events'
|
||||
import { Database } from '../database'
|
||||
|
||||
export class ConnectedDevicesManager {
|
||||
private deviceConnectionCounters = new Map<string, number>()
|
||||
// event name = key, value = boolean/ isConnected
|
||||
deviceConnectionChangeEmitter = new EventEmitter()
|
||||
|
||||
static buildKey = ({ familyId, deviceId }: {familyId: string, deviceId: string}) => `${familyId}_${deviceId}`
|
||||
|
||||
reportDeviceConnected = ({ key }: {key: string}) => {
|
||||
const oldValue = this.deviceConnectionCounters.get(key) || 0
|
||||
const newValue = oldValue + 1
|
||||
|
||||
this.deviceConnectionCounters.set(key, newValue)
|
||||
|
||||
if (oldValue === 0) {
|
||||
this.deviceConnectionChangeEmitter.emit(key, true)
|
||||
}
|
||||
}
|
||||
|
||||
reportDeviceDisconnected = ({ key }: {key: string}) => {
|
||||
const oldValue = this.deviceConnectionCounters.get(key) || 0
|
||||
const newValue = Math.max(0, oldValue - 1)
|
||||
|
||||
if (newValue === 0) {
|
||||
this.deviceConnectionCounters.delete(key)
|
||||
} else {
|
||||
this.deviceConnectionCounters.set(key, newValue)
|
||||
}
|
||||
|
||||
if (oldValue !== 0 && newValue === 0) {
|
||||
this.deviceConnectionChangeEmitter.emit(key, false)
|
||||
}
|
||||
}
|
||||
|
||||
isDeviceConnected = ({ key }: {key: string}) => (
|
||||
this.deviceConnectionCounters.has(key) &&
|
||||
(this.deviceConnectionCounters.get(key) !== 0)
|
||||
)
|
||||
}
|
||||
|
||||
export class VisibleConnectedDevicesManager {
|
||||
connectedDevicesManager = new ConnectedDevicesManager()
|
||||
private database: Database
|
||||
|
||||
constructor ({ database }: {
|
||||
database: Database
|
||||
}) {
|
||||
this.database = database
|
||||
}
|
||||
|
||||
private familyDeviceShareConnectedChangeEmitter = new EventEmitter()
|
||||
|
||||
notifyShareConnectedChanged = ({ familyId, deviceId, showDeviceConnected }: { familyId: string, deviceId: string, showDeviceConnected: boolean }) => {
|
||||
this.familyDeviceShareConnectedChangeEmitter.emit(familyId, { deviceId, showDeviceConnected })
|
||||
}
|
||||
|
||||
observeConnectedDevicesOfFamily = ({ familyId, listener }: {
|
||||
familyId: string
|
||||
listener: (deviceIds: Array<string>) => void
|
||||
}): {
|
||||
shutdown: () => void
|
||||
} => {
|
||||
let observesDevices = new Set<string>()
|
||||
let devicesWithSharingEnabled = new Set<string>()
|
||||
let connectedDevices = new Set<string>()
|
||||
let sentConnectedDevices = new Set<string>()
|
||||
let hasShutDown = false
|
||||
let shutdownHooks: Array<() => void> = []
|
||||
|
||||
const shutdown = () => {
|
||||
hasShutDown = true
|
||||
|
||||
shutdownHooks.forEach((hook) => hook())
|
||||
}
|
||||
|
||||
const sendStatus = () => {
|
||||
if (hasShutDown) {
|
||||
return
|
||||
}
|
||||
|
||||
let result: Array<string> = []
|
||||
|
||||
sentConnectedDevices.forEach((deviceId) => result.push(deviceId))
|
||||
|
||||
listener(result)
|
||||
}
|
||||
|
||||
const addDevice = ({ deviceId, showDeviceConnected }: {
|
||||
deviceId: string
|
||||
showDeviceConnected: boolean
|
||||
}) => {
|
||||
const key = ConnectedDevicesManager.buildKey({ familyId, deviceId })
|
||||
const isConnected = this.connectedDevicesManager.isDeviceConnected({ key })
|
||||
|
||||
if (!observesDevices.has(deviceId)) {
|
||||
observesDevices.add(deviceId)
|
||||
|
||||
if (isConnected) {
|
||||
connectedDevices.add(deviceId)
|
||||
}
|
||||
|
||||
if (!hasShutDown) {
|
||||
const listener = (nowConnected: boolean) => {
|
||||
const oldConnected = connectedDevices.has(deviceId)
|
||||
|
||||
if (oldConnected !== nowConnected) {
|
||||
if (nowConnected) {
|
||||
connectedDevices.add(deviceId)
|
||||
|
||||
if (devicesWithSharingEnabled.has(deviceId)) {
|
||||
sentConnectedDevices.add(deviceId)
|
||||
sendStatus()
|
||||
}
|
||||
} else {
|
||||
connectedDevices.delete(deviceId)
|
||||
|
||||
if (devicesWithSharingEnabled.has(deviceId)) {
|
||||
sentConnectedDevices.delete(deviceId)
|
||||
sendStatus()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.connectedDevicesManager.deviceConnectionChangeEmitter.addListener(key, listener)
|
||||
|
||||
shutdownHooks.push(() => {
|
||||
this.connectedDevicesManager.deviceConnectionChangeEmitter.removeListener(key, listener)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if (showDeviceConnected) {
|
||||
if (!devicesWithSharingEnabled.has(deviceId)) {
|
||||
devicesWithSharingEnabled.add(deviceId)
|
||||
}
|
||||
|
||||
if (isConnected) {
|
||||
if (!sentConnectedDevices.has(deviceId)) {
|
||||
sentConnectedDevices.add(deviceId)
|
||||
sendStatus()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (devicesWithSharingEnabled.has(deviceId)) {
|
||||
devicesWithSharingEnabled.delete(deviceId)
|
||||
|
||||
if (sentConnectedDevices.has(deviceId)) {
|
||||
sentConnectedDevices.delete(deviceId)
|
||||
sendStatus()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// add all current devices
|
||||
;(async () => {
|
||||
const devicesUnsafe = await this.database.device.findAll({
|
||||
where: {
|
||||
familyId
|
||||
},
|
||||
attributes: [
|
||||
'deviceId',
|
||||
'showDeviceConnected'
|
||||
]
|
||||
})
|
||||
|
||||
const devices = devicesUnsafe.map(({ deviceId, showDeviceConnected }) => ({
|
||||
deviceId, showDeviceConnected
|
||||
}))
|
||||
|
||||
devices.forEach(({ deviceId, showDeviceConnected }) => {
|
||||
addDevice({ deviceId, showDeviceConnected })
|
||||
})
|
||||
})().catch((ex) => { /* ignore */ })
|
||||
|
||||
{
|
||||
// add all new devices + apply changes of sharing
|
||||
const listener = ({ deviceId, showDeviceConnected }: {
|
||||
deviceId: string
|
||||
showDeviceConnected: boolean
|
||||
}) => {
|
||||
addDevice({
|
||||
deviceId,
|
||||
showDeviceConnected
|
||||
})
|
||||
}
|
||||
|
||||
this.familyDeviceShareConnectedChangeEmitter.addListener(familyId, listener)
|
||||
shutdownHooks.push(() => this.familyDeviceShareConnectedChangeEmitter.removeListener(familyId, listener))
|
||||
}
|
||||
|
||||
return { shutdown }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* server component for the TimeLimit App
|
||||
* Copyright (C) 2019 Jonas Lochmann
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import * as Sequelize from 'sequelize'
|
||||
import { familyIdColumn, idWithinFamilyColumn, labelColumn, timestampColumn } from './columns'
|
||||
import { SequelizeAttributes } from './types'
|
||||
|
||||
export interface AddDeviceTokenAttributes {
|
||||
token: string
|
||||
familyId: string
|
||||
deviceId: string
|
||||
createdAt: string
|
||||
}
|
||||
|
||||
export type AddDeviceTokenInstance = Sequelize.Instance<AddDeviceTokenAttributes> & AddDeviceTokenAttributes
|
||||
export type AddDeviceTokenModel = Sequelize.Model<AddDeviceTokenInstance, AddDeviceTokenAttributes>
|
||||
|
||||
export const attributes: SequelizeAttributes<AddDeviceTokenAttributes> = {
|
||||
token: {
|
||||
...labelColumn,
|
||||
primaryKey: true
|
||||
},
|
||||
familyId: { ...familyIdColumn },
|
||||
deviceId: { ...idWithinFamilyColumn },
|
||||
createdAt: { ...timestampColumn }
|
||||
}
|
||||
|
||||
export const createAddDeviceTokenModel = (sequelize: Sequelize.Sequelize): AddDeviceTokenModel => sequelize.define<AddDeviceTokenInstance, AddDeviceTokenInstance>('AddDeviceToken', attributes)
|
||||
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* server component for the TimeLimit App
|
||||
* Copyright (C) 2019 Jonas Lochmann
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import * as Sequelize from 'sequelize'
|
||||
import { AppRecommendation, appRecommendationValues } from '../model/apprecommendation'
|
||||
import { booleanColumn, createEnumColumn, familyIdColumn, idWithinFamilyColumn, optionalLabelColumn } from './columns'
|
||||
import { SequelizeAttributes } from './types'
|
||||
|
||||
export interface AppAttributes {
|
||||
familyId: string
|
||||
deviceId: string
|
||||
packageName: string
|
||||
title: string
|
||||
isLaunchable: boolean
|
||||
recommendation: AppRecommendation
|
||||
}
|
||||
|
||||
export type AppInstance = Sequelize.Instance<AppAttributes> & AppAttributes
|
||||
export type AppModel = Sequelize.Model<AppInstance, AppAttributes>
|
||||
|
||||
export const attributes: SequelizeAttributes<AppAttributes> = {
|
||||
familyId: {
|
||||
...familyIdColumn,
|
||||
primaryKey: true
|
||||
},
|
||||
deviceId: {
|
||||
...idWithinFamilyColumn,
|
||||
primaryKey: true
|
||||
},
|
||||
packageName: {
|
||||
type: Sequelize.STRING,
|
||||
allowNull: false,
|
||||
validate: {
|
||||
notEmpty: true
|
||||
},
|
||||
primaryKey: true
|
||||
},
|
||||
title: { ...optionalLabelColumn },
|
||||
isLaunchable: { ...booleanColumn },
|
||||
recommendation: createEnumColumn(appRecommendationValues)
|
||||
}
|
||||
|
||||
export const createAppModel = (sequelize: Sequelize.Sequelize): AppModel => sequelize.define<AppInstance, AppAttributes>('App', attributes)
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* server component for the TimeLimit App
|
||||
* Copyright (C) 2019 Jonas Lochmann
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import * as Sequelize from 'sequelize'
|
||||
import { authTokenColumn, timestampColumn } from './columns'
|
||||
import { SequelizeAttributes } from './types'
|
||||
|
||||
export interface AuthTokenAttributes {
|
||||
token: string
|
||||
mail: string
|
||||
createdAt: string
|
||||
}
|
||||
|
||||
export type AuthTokenInstance = Sequelize.Instance<AuthTokenAttributes> & AuthTokenAttributes
|
||||
export type AuthTokenModel = Sequelize.Model<AuthTokenInstance, AuthTokenAttributes>
|
||||
|
||||
export const attributes: SequelizeAttributes<AuthTokenAttributes> = {
|
||||
token: {
|
||||
...authTokenColumn,
|
||||
primaryKey: true
|
||||
},
|
||||
mail: {
|
||||
type: Sequelize.STRING,
|
||||
allowNull: false,
|
||||
validate: {
|
||||
notEmpty: true
|
||||
}
|
||||
},
|
||||
createdAt: { ...timestampColumn }
|
||||
}
|
||||
|
||||
export const createAuthtokenModel = (sequelize: Sequelize.Sequelize): AuthTokenModel => sequelize.define<AuthTokenInstance, AuthTokenAttributes>('AuthToken', attributes)
|
||||
@@ -0,0 +1,90 @@
|
||||
/*
|
||||
* server component for the TimeLimit App
|
||||
* Copyright (C) 2019 Jonas Lochmann
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import * as Sequelize from 'sequelize'
|
||||
import { serializedBitmaskRegex } from '../util/bitmask'
|
||||
import { booleanColumn, familyIdColumn, idWithinFamilyColumn, labelColumn, optionalIdWithinFamilyColumn, versionColumn } from './columns'
|
||||
import { SequelizeAttributes } from './types'
|
||||
|
||||
export interface CategoryAttributesVersion1 {
|
||||
familyId: string
|
||||
categoryId: string
|
||||
childId: string
|
||||
title: string
|
||||
blockedMinutesInWeek: string
|
||||
extraTimeInMillis: number
|
||||
temporarilyBlocked: boolean
|
||||
baseVersion: string
|
||||
assignedAppsVersion: string
|
||||
timeLimitRulesVersion: string
|
||||
usedTimesVersion: string
|
||||
}
|
||||
|
||||
export interface CategoryAttributesVersion2 {
|
||||
parentCategoryId: string
|
||||
}
|
||||
|
||||
export type CategoryAttributes = CategoryAttributesVersion1 & CategoryAttributesVersion2
|
||||
|
||||
export type CategoryInstance = Sequelize.Instance<CategoryAttributes> & CategoryAttributes
|
||||
export type CategoryModel = Sequelize.Model<CategoryInstance, CategoryAttributes>
|
||||
|
||||
export const attributesVersion1: SequelizeAttributes<CategoryAttributesVersion1> = {
|
||||
familyId: {
|
||||
...familyIdColumn,
|
||||
primaryKey: true
|
||||
},
|
||||
categoryId: {
|
||||
...idWithinFamilyColumn,
|
||||
primaryKey: true
|
||||
},
|
||||
childId: { ...idWithinFamilyColumn },
|
||||
title: { ...labelColumn },
|
||||
blockedMinutesInWeek: {
|
||||
type: Sequelize.TEXT,
|
||||
allowNull: false,
|
||||
validate: {
|
||||
is: serializedBitmaskRegex
|
||||
}
|
||||
},
|
||||
extraTimeInMillis: {
|
||||
type: Sequelize.INTEGER,
|
||||
allowNull: false,
|
||||
validate: {
|
||||
min: 0
|
||||
}
|
||||
},
|
||||
temporarilyBlocked: { ...booleanColumn },
|
||||
baseVersion: { ...versionColumn },
|
||||
assignedAppsVersion: { ...versionColumn },
|
||||
timeLimitRulesVersion: { ...versionColumn },
|
||||
usedTimesVersion: { ...versionColumn }
|
||||
}
|
||||
|
||||
export const attributesVersion2: SequelizeAttributes<CategoryAttributesVersion2> = {
|
||||
parentCategoryId: {
|
||||
...optionalIdWithinFamilyColumn,
|
||||
defaultValue: ''
|
||||
}
|
||||
}
|
||||
|
||||
export const attributes: SequelizeAttributes<CategoryAttributes> = {
|
||||
...attributesVersion1,
|
||||
...attributesVersion2
|
||||
}
|
||||
|
||||
export const createCategoryModel = (sequelize: Sequelize.Sequelize): CategoryModel => sequelize.define<CategoryInstance, CategoryAttributes>('Category', attributes)
|
||||
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* server component for the TimeLimit App
|
||||
* Copyright (C) 2019 Jonas Lochmann
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import * as Sequelize from 'sequelize'
|
||||
import { familyIdColumn, idWithinFamilyColumn } from './columns'
|
||||
import { SequelizeAttributes } from './types'
|
||||
|
||||
export interface CategoryAppAttributes {
|
||||
familyId: string
|
||||
categoryId: string
|
||||
packageName: string
|
||||
}
|
||||
|
||||
export type CategoryAppInstance = Sequelize.Instance<CategoryAppAttributes> & CategoryAppAttributes
|
||||
export type CategoryAppModel = Sequelize.Model<CategoryAppInstance, CategoryAppAttributes>
|
||||
|
||||
export const attributes: SequelizeAttributes<CategoryAppAttributes> = {
|
||||
familyId: {
|
||||
...familyIdColumn,
|
||||
primaryKey: true
|
||||
},
|
||||
categoryId: {
|
||||
...idWithinFamilyColumn,
|
||||
primaryKey: true
|
||||
},
|
||||
packageName: {
|
||||
type: Sequelize.STRING,
|
||||
allowNull: false,
|
||||
validate: {
|
||||
notEmpty: true
|
||||
},
|
||||
primaryKey: true
|
||||
}
|
||||
}
|
||||
|
||||
export const createCategoryAppModel = (sequelize: Sequelize.Sequelize): CategoryAppModel => sequelize.define<CategoryAppInstance, CategoryAppAttributes>('CategoryApp', attributes)
|
||||
@@ -0,0 +1,97 @@
|
||||
/*
|
||||
* server component for the TimeLimit App
|
||||
* Copyright (C) 2019 Jonas Lochmann
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import * as Sequelize from 'sequelize'
|
||||
|
||||
export const familyIdColumn: Sequelize.DefineAttributeColumnOptions = {
|
||||
type: Sequelize.STRING(10),
|
||||
allowNull: false,
|
||||
validate: {
|
||||
notEmpty: true,
|
||||
is: /^[a-zA-Z0-9]{10}$/
|
||||
}
|
||||
}
|
||||
|
||||
export const idWithinFamilyColumn: Sequelize.DefineAttributeColumnOptions = {
|
||||
type: Sequelize.STRING(6),
|
||||
allowNull: false,
|
||||
validate: {
|
||||
notEmpty: true,
|
||||
is: /^[a-zA-Z0-9]{6}$/
|
||||
}
|
||||
}
|
||||
|
||||
export const optionalIdWithinFamilyColumn: Sequelize.DefineAttributeColumnOptions = {
|
||||
type: Sequelize.STRING(6),
|
||||
allowNull: false,
|
||||
validate: {
|
||||
is: /^([a-zA-Z0-9]{6})?$/
|
||||
}
|
||||
}
|
||||
|
||||
export const versionColumn: Sequelize.DefineAttributeColumnOptions = {
|
||||
type: Sequelize.STRING(4),
|
||||
allowNull: false,
|
||||
validate: {
|
||||
notEmpty: true,
|
||||
is: /^[a-zA-Z0-9]{4}$/
|
||||
}
|
||||
}
|
||||
export const labelColumn: Sequelize.DefineAttributeColumnOptions = {
|
||||
type: Sequelize.STRING,
|
||||
allowNull: false,
|
||||
validate: {
|
||||
notEmpty: true
|
||||
}
|
||||
}
|
||||
|
||||
export const optionalLabelColumn: Sequelize.DefineAttributeColumnOptions = {
|
||||
type: Sequelize.STRING,
|
||||
allowNull: false
|
||||
}
|
||||
|
||||
export const createEnumColumn = (possibleValues: Array<string>): Sequelize.DefineAttributeColumnOptions => ({
|
||||
type: Sequelize.STRING,
|
||||
allowNull: false,
|
||||
validate: {
|
||||
isIn: [possibleValues],
|
||||
notEmpty: true
|
||||
}
|
||||
})
|
||||
|
||||
// warning: this results in an string field
|
||||
export const timestampColumn: Sequelize.DefineAttributeColumnOptions = {
|
||||
type: Sequelize.BIGINT,
|
||||
allowNull: false,
|
||||
validate: {
|
||||
min: 0
|
||||
}
|
||||
}
|
||||
|
||||
export const booleanColumn: Sequelize.DefineAttributeColumnOptions = {
|
||||
type: Sequelize.BOOLEAN,
|
||||
allowNull: false
|
||||
}
|
||||
|
||||
export const authTokenColumn: Sequelize.DefineAttributeColumnOptions = {
|
||||
type: Sequelize.STRING(32),
|
||||
allowNull: false,
|
||||
validate: {
|
||||
notEmpty: true,
|
||||
is: /^[a-zA-Z0-9]{32}$/
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,204 @@
|
||||
/*
|
||||
* server component for the TimeLimit App
|
||||
* Copyright (C) 2019 Jonas Lochmann
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import * as Sequelize from 'sequelize'
|
||||
import { NewPermissionStatus, newPermissionStatusValues } from '../model/newpermissionstatus'
|
||||
import { ProtectionLevel, protetionLevels } from '../model/protectionlevel'
|
||||
import { RuntimePermissionStatus, runtimePermissionStatusValues } from '../model/runtimepermissionstatus'
|
||||
import { authTokenColumn, booleanColumn, createEnumColumn, familyIdColumn, idWithinFamilyColumn, labelColumn, optionalIdWithinFamilyColumn, timestampColumn, versionColumn } from './columns'
|
||||
import { SequelizeAttributes } from './types'
|
||||
|
||||
export interface DeviceAttributesVersion1 {
|
||||
familyId: string
|
||||
deviceId: string
|
||||
name: string
|
||||
model: string
|
||||
addedAt: string
|
||||
currentUserId: string
|
||||
installedAppsVersion: string
|
||||
deviceAuthToken: string
|
||||
networkTime: 'disabled' | 'if possible' | 'enabled'
|
||||
nextSequenceNumber: number
|
||||
currentProtectionLevel: ProtectionLevel
|
||||
highestProtectionLevel: ProtectionLevel
|
||||
currentUsageStatsPermission: RuntimePermissionStatus
|
||||
highestUsageStatsPermission: RuntimePermissionStatus
|
||||
currentNotificationAccessPermission: NewPermissionStatus
|
||||
highestNotificationAccessPermission: NewPermissionStatus
|
||||
currentAppVersion: number
|
||||
highestAppVersion: number
|
||||
triedDisablingDeviceAdmin: boolean
|
||||
hadManipulation: boolean
|
||||
}
|
||||
|
||||
export interface DeviceAttributesVersion2 {
|
||||
lastConnectivity: string
|
||||
notSeenForLongTime: boolean
|
||||
didDeviceReportUninstall: boolean
|
||||
}
|
||||
|
||||
export interface DeviceAttributesVersion3 {
|
||||
isUserKeptSignedIn: boolean
|
||||
}
|
||||
|
||||
export interface DeviceAttributesVersion4 {
|
||||
showDeviceConnected: boolean
|
||||
}
|
||||
|
||||
export interface DeviceAttributesVersion5 {
|
||||
defaultUserId: string
|
||||
defaultUserTimeout: number
|
||||
}
|
||||
|
||||
export interface DeviceAttributesVersion6 {
|
||||
didReboot: boolean
|
||||
considerRebootManipulation: boolean
|
||||
}
|
||||
|
||||
export type DeviceAttributes = DeviceAttributesVersion1 & DeviceAttributesVersion2 &
|
||||
DeviceAttributesVersion3 & DeviceAttributesVersion4 & DeviceAttributesVersion5 &
|
||||
DeviceAttributesVersion6
|
||||
|
||||
export type DeviceInstance = Sequelize.Instance<DeviceAttributes> & DeviceAttributes
|
||||
export type DeviceModel = Sequelize.Model<DeviceInstance, DeviceAttributes>
|
||||
|
||||
export const attributesVersion1: SequelizeAttributes<DeviceAttributesVersion1> = {
|
||||
familyId: {
|
||||
...familyIdColumn,
|
||||
primaryKey: true
|
||||
},
|
||||
deviceId: {
|
||||
...idWithinFamilyColumn,
|
||||
primaryKey: true
|
||||
},
|
||||
name: { ...labelColumn },
|
||||
model: { ...labelColumn },
|
||||
addedAt: { ...timestampColumn },
|
||||
currentUserId: { ...optionalIdWithinFamilyColumn },
|
||||
installedAppsVersion: { ...versionColumn },
|
||||
deviceAuthToken: { ...authTokenColumn },
|
||||
networkTime: createEnumColumn(['disabled', 'if possible', 'enabled']),
|
||||
nextSequenceNumber: {
|
||||
type: Sequelize.INTEGER,
|
||||
allowNull: false
|
||||
},
|
||||
currentProtectionLevel: createEnumColumn(protetionLevels),
|
||||
highestProtectionLevel: createEnumColumn(protetionLevels),
|
||||
currentUsageStatsPermission: createEnumColumn(runtimePermissionStatusValues),
|
||||
highestUsageStatsPermission: createEnumColumn(runtimePermissionStatusValues),
|
||||
currentNotificationAccessPermission: createEnumColumn(newPermissionStatusValues),
|
||||
highestNotificationAccessPermission: createEnumColumn(newPermissionStatusValues),
|
||||
currentAppVersion: {
|
||||
type: Sequelize.INTEGER,
|
||||
allowNull: false,
|
||||
validate: {
|
||||
min: 0
|
||||
}
|
||||
},
|
||||
highestAppVersion: {
|
||||
type: Sequelize.INTEGER,
|
||||
allowNull: false,
|
||||
validate: {
|
||||
min: 0
|
||||
}
|
||||
},
|
||||
triedDisablingDeviceAdmin: { ...booleanColumn },
|
||||
hadManipulation: { ...booleanColumn }
|
||||
}
|
||||
|
||||
export const attributesVersion2: SequelizeAttributes<DeviceAttributesVersion2> = {
|
||||
lastConnectivity: {
|
||||
...timestampColumn,
|
||||
defaultValue: 0
|
||||
},
|
||||
notSeenForLongTime: {
|
||||
...booleanColumn,
|
||||
defaultValue: false
|
||||
},
|
||||
didDeviceReportUninstall: {
|
||||
...booleanColumn,
|
||||
defaultValue: false
|
||||
}
|
||||
}
|
||||
|
||||
export const attributesVersion3: SequelizeAttributes<DeviceAttributesVersion3> = {
|
||||
isUserKeptSignedIn: {
|
||||
...booleanColumn,
|
||||
defaultValue: false
|
||||
}
|
||||
}
|
||||
|
||||
export const attributesVersion4: SequelizeAttributes<DeviceAttributesVersion4> = {
|
||||
showDeviceConnected: {
|
||||
...booleanColumn,
|
||||
defaultValue: false
|
||||
}
|
||||
}
|
||||
|
||||
export const attributesVersion5: SequelizeAttributes<DeviceAttributesVersion5> = {
|
||||
defaultUserId: {
|
||||
...optionalIdWithinFamilyColumn,
|
||||
defaultValue: ''
|
||||
},
|
||||
defaultUserTimeout: {
|
||||
type: Sequelize.INTEGER,
|
||||
allowNull: false,
|
||||
defaultValue: 0,
|
||||
validate: {
|
||||
min: 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const attributesVersion6: SequelizeAttributes<DeviceAttributesVersion6> = {
|
||||
didReboot: {
|
||||
...booleanColumn,
|
||||
defaultValue: false
|
||||
},
|
||||
considerRebootManipulation: {
|
||||
...booleanColumn,
|
||||
defaultValue: false
|
||||
}
|
||||
}
|
||||
|
||||
export const attributes: SequelizeAttributes<DeviceAttributes> = {
|
||||
...attributesVersion1,
|
||||
...attributesVersion2,
|
||||
...attributesVersion3,
|
||||
...attributesVersion4,
|
||||
...attributesVersion5,
|
||||
...attributesVersion6
|
||||
}
|
||||
|
||||
export const createDeviceModel = (sequelize: Sequelize.Sequelize): DeviceModel => sequelize.define<DeviceInstance, DeviceAttributes>('Device', attributes)
|
||||
export const hasDeviceManipulation = (device: DeviceAttributes) => {
|
||||
const manipulationOfProtectionLevel = device.currentProtectionLevel !== device.highestProtectionLevel
|
||||
const manipulationOfUsageStats = device.currentUsageStatsPermission !== device.highestUsageStatsPermission
|
||||
const manipulationOfNotificationAccess = device.currentNotificationAccessPermission !== device.highestNotificationAccessPermission
|
||||
const manipulationOfAppVersion = device.currentAppVersion !== device.highestAppVersion
|
||||
|
||||
const hasActiveManipulationWarning = manipulationOfProtectionLevel ||
|
||||
manipulationOfUsageStats ||
|
||||
manipulationOfNotificationAccess ||
|
||||
manipulationOfAppVersion ||
|
||||
device.triedDisablingDeviceAdmin ||
|
||||
device.didReboot
|
||||
|
||||
const hasAnyManipulation = hasActiveManipulationWarning || device.hadManipulation
|
||||
|
||||
return hasAnyManipulation
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* server component for the TimeLimit App
|
||||
* Copyright (C) 2019 Jonas Lochmann
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import * as Sequelize from 'sequelize'
|
||||
import { booleanColumn, familyIdColumn, optionalLabelColumn, timestampColumn, versionColumn } from './columns'
|
||||
import { SequelizeAttributes } from './types'
|
||||
|
||||
export interface FamilyAttributes {
|
||||
familyId: string
|
||||
name: string
|
||||
createdAt: string
|
||||
userListVersion: string
|
||||
deviceListVersion: string
|
||||
fullVersionUntil: string
|
||||
hasFullVersion: boolean
|
||||
}
|
||||
|
||||
export type FamilyInstance = Sequelize.Instance<FamilyAttributes> & FamilyAttributes
|
||||
export type FamilyModel = Sequelize.Model<FamilyInstance, FamilyAttributes>
|
||||
|
||||
export const attributes: SequelizeAttributes<FamilyAttributes> = {
|
||||
familyId: {
|
||||
...familyIdColumn,
|
||||
primaryKey: true
|
||||
},
|
||||
name: { ...optionalLabelColumn },
|
||||
createdAt: { ...timestampColumn },
|
||||
userListVersion: { ...versionColumn },
|
||||
deviceListVersion: { ...versionColumn },
|
||||
fullVersionUntil: { ...timestampColumn },
|
||||
hasFullVersion: { ...booleanColumn }
|
||||
}
|
||||
|
||||
export const createFamilyModel = (sequelize: Sequelize.Sequelize): FamilyModel => sequelize.define<FamilyInstance, FamilyAttributes>('Family', attributes)
|
||||
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
* server component for the TimeLimit App
|
||||
* Copyright (C) 2019 Jonas Lochmann
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import * as Sequelize from 'sequelize'
|
||||
import { AddDeviceTokenModel, createAddDeviceTokenModel } from './adddevicetoken'
|
||||
import { AppModel, createAppModel } from './app'
|
||||
import { AuthTokenModel, createAuthtokenModel } from './authtoken'
|
||||
import { CategoryModel, createCategoryModel } from './category'
|
||||
import { CategoryAppModel, createCategoryAppModel } from './categoryapp'
|
||||
import { createDeviceModel, DeviceModel } from './device'
|
||||
import { createFamilyModel, FamilyModel } from './family'
|
||||
import { createMailLoginTokenModel, MailLoginTokenModel } from './maillogintoken'
|
||||
import { createUmzug } from './migration/umzug'
|
||||
import { createOldDeviceModel, OldDeviceModel } from './olddevice'
|
||||
import { createPurchaseModel, PurchaseModel } from './purchase'
|
||||
import { createTimelimitRuleModel, TimelimitRuleModel } from './timelimitrule'
|
||||
import { createUsedTimeModel, UsedTimeModel } from './usedtime'
|
||||
import { createUserModel, UserModel } from './user'
|
||||
|
||||
export interface Database {
|
||||
addDeviceToken: AddDeviceTokenModel
|
||||
authtoken: AuthTokenModel
|
||||
app: AppModel
|
||||
category: CategoryModel
|
||||
categoryApp: CategoryAppModel
|
||||
device: DeviceModel
|
||||
family: FamilyModel
|
||||
mailLoginToken: MailLoginTokenModel
|
||||
oldDevice: OldDeviceModel
|
||||
purchase: PurchaseModel
|
||||
timelimitRule: TimelimitRuleModel
|
||||
usedTime: UsedTimeModel
|
||||
user: UserModel
|
||||
transaction: <T> (autoCallback: (t: Sequelize.Transaction) => Promise<T>) => Promise<T>
|
||||
}
|
||||
|
||||
const createDatabase = (sequelize: Sequelize.Sequelize): Database => ({
|
||||
addDeviceToken: createAddDeviceTokenModel(sequelize),
|
||||
authtoken: createAuthtokenModel(sequelize),
|
||||
app: createAppModel(sequelize),
|
||||
category: createCategoryModel(sequelize),
|
||||
categoryApp: createCategoryAppModel(sequelize),
|
||||
device: createDeviceModel(sequelize),
|
||||
family: createFamilyModel(sequelize),
|
||||
mailLoginToken: createMailLoginTokenModel(sequelize),
|
||||
oldDevice: createOldDeviceModel(sequelize),
|
||||
purchase: createPurchaseModel(sequelize),
|
||||
timelimitRule: createTimelimitRuleModel(sequelize),
|
||||
usedTime: createUsedTimeModel(sequelize),
|
||||
user: createUserModel(sequelize),
|
||||
transaction: <T> (autoCallback: (transaction: Sequelize.Transaction) => Promise<T>) => (sequelize.transaction({
|
||||
isolationLevel: Sequelize.Transaction.ISOLATION_LEVELS.READ_COMMITTED
|
||||
}, autoCallback) as any) as Promise<T>
|
||||
})
|
||||
|
||||
export const sequelize = new Sequelize(process.env.DATABASE_URL || 'sqlite://test.db', {
|
||||
define: {
|
||||
timestamps: false
|
||||
},
|
||||
operatorsAliases: false,
|
||||
logging: false
|
||||
})
|
||||
|
||||
export const defaultDatabase = createDatabase(sequelize)
|
||||
export const defaultUmzug = createUmzug(sequelize)
|
||||
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* server component for the TimeLimit App
|
||||
* Copyright (C) 2019 Jonas Lochmann
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import * as Sequelize from 'sequelize'
|
||||
import { authTokenColumn, timestampColumn } from './columns'
|
||||
import { SequelizeAttributes } from './types'
|
||||
|
||||
export interface MailLoginTokenAttributes {
|
||||
mailLoginToken: string
|
||||
receivedCode: string
|
||||
mail: string
|
||||
createdAt: string
|
||||
remainingAttempts: number
|
||||
}
|
||||
|
||||
export type MailLoginTokenInstance = Sequelize.Instance<MailLoginTokenAttributes> & MailLoginTokenAttributes
|
||||
export type MailLoginTokenModel = Sequelize.Model<MailLoginTokenInstance, MailLoginTokenAttributes>
|
||||
|
||||
export const attributes: SequelizeAttributes<MailLoginTokenAttributes> = {
|
||||
mailLoginToken: {
|
||||
...authTokenColumn,
|
||||
primaryKey: true
|
||||
},
|
||||
receivedCode: {
|
||||
type: Sequelize.STRING,
|
||||
allowNull: false,
|
||||
validate: {
|
||||
notEmpty: true
|
||||
}
|
||||
},
|
||||
mail: {
|
||||
type: Sequelize.STRING,
|
||||
allowNull: false,
|
||||
validate: {
|
||||
notEmpty: true
|
||||
}
|
||||
},
|
||||
createdAt: { ...timestampColumn },
|
||||
remainingAttempts: {
|
||||
type: Sequelize.INTEGER,
|
||||
allowNull: false,
|
||||
validate: {
|
||||
min: 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const createMailLoginTokenModel = (sequelize: Sequelize.Sequelize): MailLoginTokenModel => sequelize.define<MailLoginTokenInstance, MailLoginTokenAttributes>('MailLoginToken', attributes)
|
||||
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* server component for the TimeLimit App
|
||||
* Copyright (C) 2019 Jonas Lochmann
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { QueryInterface, Sequelize } from 'sequelize'
|
||||
import { attributes as addDeviceTokenAttributes } from '../../adddevicetoken'
|
||||
import { attributes as appAttributes } from '../../app'
|
||||
import { attributes as authTokenAttributes } from '../../authtoken'
|
||||
import { attributesVersion1 as categoryAttributes } from '../../category'
|
||||
import { attributes as categoryAppAttributes } from '../../categoryapp'
|
||||
import { attributesVersion1 as deviceAttributes } from '../../device'
|
||||
import { attributes as familyAttributes } from '../../family'
|
||||
import { attributes as purchaseAttributes } from '../../purchase'
|
||||
import { attributes as timelimitruleAttributes } from '../../timelimitrule'
|
||||
import { attributesVersion1 as usedTimeAttribute } from '../../usedtime'
|
||||
import { attributesVersion1 as userAttributes } from '../../user'
|
||||
|
||||
export async function up (queryInterface: QueryInterface, sequelize: Sequelize) {
|
||||
await sequelize.transaction({
|
||||
type: 'EXCLUSIVE'
|
||||
}, async (transaction) => {
|
||||
await queryInterface.createTable('AddDeviceTokens', addDeviceTokenAttributes, { transaction })
|
||||
await queryInterface.createTable('Apps', appAttributes, { transaction })
|
||||
await queryInterface.createTable('AuthTokens', authTokenAttributes, { transaction })
|
||||
await queryInterface.createTable('Categories', categoryAttributes, { transaction })
|
||||
await queryInterface.createTable('CategoryApps', categoryAppAttributes, { transaction })
|
||||
await queryInterface.createTable('Devices', deviceAttributes, { transaction })
|
||||
await queryInterface.createTable('Families', familyAttributes, { transaction })
|
||||
await queryInterface.createTable('Purchases', purchaseAttributes, { transaction })
|
||||
await queryInterface.createTable('TimelimitRules', timelimitruleAttributes, { transaction })
|
||||
await queryInterface.createTable('UsedTimes', usedTimeAttribute, { transaction })
|
||||
await queryInterface.createTable('Users', userAttributes, { transaction })
|
||||
})
|
||||
}
|
||||
|
||||
export async function down () {
|
||||
throw new Error('not possible')
|
||||
}
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* server component for the TimeLimit App
|
||||
* Copyright (C) 2019 Jonas Lochmann
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { QueryInterface, Sequelize } from 'sequelize'
|
||||
import { attributesVersion2 } from '../../device'
|
||||
import { attributes as oldDeviceAttributes } from '../../olddevice'
|
||||
|
||||
export async function up (queryInterface: QueryInterface, sequelize: Sequelize) {
|
||||
await sequelize.transaction({
|
||||
type: 'EXCLUSIVE'
|
||||
}, async (transaction) => {
|
||||
await queryInterface.addColumn('Devices', 'lastConnectivity', {
|
||||
...attributesVersion2.lastConnectivity
|
||||
}, {
|
||||
transaction
|
||||
})
|
||||
|
||||
await queryInterface.addColumn('Devices', 'notSeenForLongTime', {
|
||||
...attributesVersion2.notSeenForLongTime
|
||||
}, {
|
||||
transaction
|
||||
})
|
||||
|
||||
await queryInterface.addColumn('Devices', 'didDeviceReportUninstall', {
|
||||
...attributesVersion2.didDeviceReportUninstall
|
||||
}, {
|
||||
transaction
|
||||
})
|
||||
|
||||
await queryInterface.createTable('OldDevices', oldDeviceAttributes, { transaction })
|
||||
})
|
||||
}
|
||||
|
||||
export async function down (queryInterface: QueryInterface, sequelize: Sequelize) {
|
||||
await sequelize.transaction({
|
||||
type: 'EXCLUSIVE'
|
||||
}, async (transaction) => {
|
||||
await queryInterface.removeColumn('Devices', 'lastConnectivity', { transaction })
|
||||
await queryInterface.removeColumn('Devices', 'notSeenForLongTime', { transaction })
|
||||
await queryInterface.removeColumn('Devices', 'didDeviceReportUninstall', { transaction })
|
||||
|
||||
await queryInterface.dropTable('OldDevices', { transaction })
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* server component for the TimeLimit App
|
||||
* Copyright (C) 2019 Jonas Lochmann
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { QueryInterface, Sequelize } from 'sequelize'
|
||||
import { attributes as mailLoginTokenAttributes } from '../../maillogintoken'
|
||||
|
||||
export async function up (queryInterface: QueryInterface, sequelize: Sequelize) {
|
||||
await sequelize.transaction({
|
||||
type: 'EXCLUSIVE'
|
||||
}, async (transaction) => {
|
||||
await queryInterface.createTable('MailLoginTokens', mailLoginTokenAttributes, { transaction })
|
||||
})
|
||||
}
|
||||
|
||||
export async function down (queryInterface: QueryInterface, sequelize: Sequelize) {
|
||||
await sequelize.transaction({
|
||||
type: 'EXCLUSIVE'
|
||||
}, async (transaction) => {
|
||||
await queryInterface.dropTable('MailLoginTokens', { transaction })
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* server component for the TimeLimit App
|
||||
* Copyright (C) 2019 Jonas Lochmann
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { QueryInterface, Sequelize } from 'sequelize'
|
||||
import { attributesVersion3 } from '../../device'
|
||||
|
||||
export async function up (queryInterface: QueryInterface, sequelize: Sequelize) {
|
||||
await sequelize.transaction({
|
||||
type: 'EXCLUSIVE'
|
||||
}, async (transaction) => {
|
||||
await queryInterface.addColumn('Devices', 'isUserKeptSignedIn', {
|
||||
...attributesVersion3.isUserKeptSignedIn
|
||||
}, {
|
||||
transaction
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
export async function down (queryInterface: QueryInterface, sequelize: Sequelize) {
|
||||
await sequelize.transaction({
|
||||
type: 'EXCLUSIVE'
|
||||
}, async (transaction) => {
|
||||
await queryInterface.removeColumn('Devices', 'isUserKeptSignedIn', { transaction })
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* server component for the TimeLimit App
|
||||
* Copyright (C) 2019 Jonas Lochmann
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { QueryInterface, Sequelize } from 'sequelize'
|
||||
import { attributesVersion2 } from '../../user'
|
||||
|
||||
export async function up (queryInterface: QueryInterface, sequelize: Sequelize) {
|
||||
await sequelize.transaction({
|
||||
type: 'EXCLUSIVE'
|
||||
}, async (transaction) => {
|
||||
await queryInterface.addColumn('Users', 'categoryForNotAssignedApps', {
|
||||
...attributesVersion2.categoryForNotAssignedApps
|
||||
}, {
|
||||
transaction
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
export async function down (queryInterface: QueryInterface, sequelize: Sequelize) {
|
||||
await sequelize.transaction({
|
||||
type: 'EXCLUSIVE'
|
||||
}, async (transaction) => {
|
||||
await queryInterface.removeColumn('Users', 'categoryForNotAssignedApps', { transaction })
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* server component for the TimeLimit App
|
||||
* Copyright (C) 2019 Jonas Lochmann
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { QueryInterface, Sequelize } from 'sequelize'
|
||||
import { attributesVersion2 } from '../../category'
|
||||
|
||||
export async function up (queryInterface: QueryInterface, sequelize: Sequelize) {
|
||||
await sequelize.transaction({
|
||||
type: 'EXCLUSIVE'
|
||||
}, async (transaction) => {
|
||||
await queryInterface.addColumn('Categories', 'parentCategoryId', {
|
||||
...attributesVersion2.parentCategoryId
|
||||
}, {
|
||||
transaction
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
export async function down (queryInterface: QueryInterface, sequelize: Sequelize) {
|
||||
await sequelize.transaction({
|
||||
type: 'EXCLUSIVE'
|
||||
}, async (transaction) => {
|
||||
await queryInterface.removeColumn('Categories', 'parentCategoryId', { transaction })
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* server component for the TimeLimit App
|
||||
* Copyright (C) 2019 Jonas Lochmann
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { QueryInterface, Sequelize } from 'sequelize'
|
||||
import { attributesVersion4 } from '../../device'
|
||||
|
||||
export async function up (queryInterface: QueryInterface, sequelize: Sequelize) {
|
||||
await sequelize.transaction({
|
||||
type: 'EXCLUSIVE'
|
||||
}, async (transaction) => {
|
||||
await queryInterface.addColumn('Devices', 'showDeviceConnected', {
|
||||
...attributesVersion4.showDeviceConnected
|
||||
}, {
|
||||
transaction
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
export async function down (queryInterface: QueryInterface, sequelize: Sequelize) {
|
||||
await sequelize.transaction({
|
||||
type: 'EXCLUSIVE'
|
||||
}, async (transaction) => {
|
||||
await queryInterface.removeColumn('Devices', 'showDeviceConnected', { transaction })
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* server component for the TimeLimit App
|
||||
* Copyright (C) 2019 Jonas Lochmann
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { QueryInterface, Sequelize } from 'sequelize'
|
||||
import { attributesVersion2 } from '../../usedtime'
|
||||
|
||||
export async function up (queryInterface: QueryInterface, sequelize: Sequelize) {
|
||||
await sequelize.transaction({
|
||||
type: 'EXCLUSIVE'
|
||||
}, async (transaction) => {
|
||||
await queryInterface.addColumn('UsedTimes', 'lastUpdate', {
|
||||
...attributesVersion2.lastUpdate
|
||||
}, {
|
||||
transaction
|
||||
})
|
||||
})
|
||||
|
||||
await queryInterface.addIndex('UsedTimes', ['lastUpdate'])
|
||||
}
|
||||
|
||||
export async function down (queryInterface: QueryInterface, sequelize: Sequelize) {
|
||||
await sequelize.transaction({
|
||||
type: 'EXCLUSIVE'
|
||||
}, async (transaction) => {
|
||||
await queryInterface.removeIndex('UsedTimes', ['lastUpdate'], { transaction })
|
||||
await queryInterface.removeColumn('UsedTimes', 'lastUpdate', { transaction })
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* server component for the TimeLimit App
|
||||
* Copyright (C) 2019 Jonas Lochmann
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { QueryInterface, Sequelize } from 'sequelize'
|
||||
import { attributesVersion5 as deviceAttributes } from '../../device'
|
||||
import { attributesVersion3 as userAttributes } from '../../user'
|
||||
|
||||
export async function up (queryInterface: QueryInterface, sequelize: Sequelize) {
|
||||
await sequelize.transaction({
|
||||
type: 'EXCLUSIVE'
|
||||
}, async (transaction) => {
|
||||
// users
|
||||
await queryInterface.addColumn('Users', 'relaxPrimaryDeviceRule', {
|
||||
...userAttributes.relaxPrimaryDeviceRule
|
||||
}, { transaction })
|
||||
|
||||
// devices
|
||||
await queryInterface.addColumn('Devices', 'defaultUserId', {
|
||||
...deviceAttributes.defaultUserId
|
||||
}, { transaction })
|
||||
|
||||
await queryInterface.addColumn('Devices', 'defaultUserTimeout', {
|
||||
...deviceAttributes.defaultUserTimeout
|
||||
}, { transaction })
|
||||
})
|
||||
}
|
||||
|
||||
export async function down (queryInterface: QueryInterface, sequelize: Sequelize) {
|
||||
await sequelize.transaction({
|
||||
type: 'EXCLUSIVE'
|
||||
}, async (transaction) => {
|
||||
// users
|
||||
await queryInterface.removeColumn('Users', 'relaxPrimaryDeviceRule', { transaction })
|
||||
|
||||
// devices
|
||||
await queryInterface.removeColumn('Devices', 'defaultUserId', { transaction })
|
||||
await queryInterface.removeColumn('Devices', 'defaultUserTimeout', { transaction })
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* server component for the TimeLimit App
|
||||
* Copyright (C) 2019 Jonas Lochmann
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { QueryInterface, Sequelize } from 'sequelize'
|
||||
import { attributesVersion6 } from '../../device'
|
||||
|
||||
export async function up (queryInterface: QueryInterface, sequelize: Sequelize) {
|
||||
await sequelize.transaction({
|
||||
type: 'EXCLUSIVE'
|
||||
}, async (transaction) => {
|
||||
await queryInterface.addColumn('Devices', 'considerRebootManipulation', {
|
||||
...attributesVersion6.considerRebootManipulation
|
||||
}, {
|
||||
transaction
|
||||
})
|
||||
|
||||
await queryInterface.addColumn('Devices', 'didReboot', {
|
||||
...attributesVersion6.didReboot
|
||||
}, {
|
||||
transaction
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
export async function down (queryInterface: QueryInterface, sequelize: Sequelize) {
|
||||
await sequelize.transaction({
|
||||
type: 'EXCLUSIVE'
|
||||
}, async (transaction) => {
|
||||
await queryInterface.removeColumn('Devices', 'considerRebootManipulation', { transaction })
|
||||
await queryInterface.removeColumn('Devices', 'didReboot', { transaction })
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* server component for the TimeLimit App
|
||||
* Copyright (C) 2019 Jonas Lochmann
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { QueryInterface, Sequelize } from 'sequelize'
|
||||
import { attributesVersion4 as userAttributes } from '../../user'
|
||||
|
||||
export async function up (queryInterface: QueryInterface, sequelize: Sequelize) {
|
||||
await sequelize.transaction({
|
||||
type: 'EXCLUSIVE'
|
||||
}, async (transaction) => {
|
||||
await queryInterface.addColumn('Users', 'mailNotificationFlags', {
|
||||
...userAttributes.mailNotificationFlags
|
||||
}, { transaction })
|
||||
})
|
||||
}
|
||||
|
||||
export async function down (queryInterface: QueryInterface, sequelize: Sequelize) {
|
||||
await sequelize.transaction({
|
||||
type: 'EXCLUSIVE'
|
||||
}, async (transaction) => {
|
||||
await queryInterface.removeColumn('Users', 'mailNotificationFlags', { transaction })
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* server component for the TimeLimit App
|
||||
* Copyright (C) 2019 Jonas Lochmann
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { resolve } from 'path'
|
||||
import { Sequelize } from 'sequelize'
|
||||
import * as Umzug from 'umzug'
|
||||
|
||||
export const createUmzug = (sequelize: Sequelize) => (
|
||||
new Umzug({
|
||||
storage: 'sequelize',
|
||||
storageOptions: {
|
||||
sequelize
|
||||
},
|
||||
migrations: {
|
||||
params: [sequelize.getQueryInterface(), sequelize],
|
||||
path: resolve(__dirname, '../../../build/database/migration/migrations'),
|
||||
pattern: /^\d+[\w-]+\.js$/
|
||||
}
|
||||
})
|
||||
)
|
||||
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* server component for the TimeLimit App
|
||||
* Copyright (C) 2019 Jonas Lochmann
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import * as Sequelize from 'sequelize'
|
||||
import { authTokenColumn } from './columns'
|
||||
import { SequelizeAttributes } from './types'
|
||||
|
||||
export interface OldDeviceAttributes {
|
||||
deviceAuthToken: string
|
||||
}
|
||||
|
||||
export type OldDeviceInstance = Sequelize.Instance<OldDeviceAttributes> & OldDeviceAttributes
|
||||
export type OldDeviceModel = Sequelize.Model<OldDeviceInstance, OldDeviceAttributes>
|
||||
|
||||
export const attributes: SequelizeAttributes<OldDeviceAttributes> = {
|
||||
deviceAuthToken: {
|
||||
...authTokenColumn,
|
||||
primaryKey: true
|
||||
}
|
||||
}
|
||||
|
||||
export const createOldDeviceModel = (sequelize: Sequelize.Sequelize): OldDeviceModel => sequelize.define<OldDeviceInstance, OldDeviceAttributes>('OldDevice', attributes)
|
||||
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* server component for the TimeLimit App
|
||||
* Copyright (C) 2019 Jonas Lochmann
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import * as Sequelize from 'sequelize'
|
||||
import { createEnumColumn, familyIdColumn, timestampColumn } from './columns'
|
||||
import { SequelizeAttributes } from './types'
|
||||
|
||||
export interface PurchaseAttributes {
|
||||
familyId: string
|
||||
service: 'googleplay'
|
||||
transactionId: string
|
||||
type: 'month' | 'year'
|
||||
loggedAt: string
|
||||
previousFullVersionEndTime: string
|
||||
newFullVersionEndTime: string
|
||||
}
|
||||
|
||||
export type PurchaseInstance = Sequelize.Instance<PurchaseAttributes> & PurchaseAttributes
|
||||
export type PurchaseModel = Sequelize.Model<PurchaseInstance, PurchaseAttributes>
|
||||
|
||||
export const attributes: SequelizeAttributes<PurchaseAttributes> = {
|
||||
familyId: { ...familyIdColumn },
|
||||
service: {
|
||||
...createEnumColumn(['googleplay']),
|
||||
primaryKey: true
|
||||
},
|
||||
transactionId: {
|
||||
type: Sequelize.STRING,
|
||||
primaryKey: true
|
||||
},
|
||||
type: createEnumColumn(['month', 'year']),
|
||||
loggedAt: timestampColumn,
|
||||
previousFullVersionEndTime: timestampColumn,
|
||||
newFullVersionEndTime: timestampColumn
|
||||
}
|
||||
|
||||
export const createPurchaseModel = (sequelize: Sequelize.Sequelize): PurchaseModel => sequelize.define<PurchaseInstance, PurchaseAttributes>('Purchase', attributes)
|
||||
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* server component for the TimeLimit App
|
||||
* Copyright (C) 2019 Jonas Lochmann
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import * as Sequelize from 'sequelize'
|
||||
import { booleanColumn, familyIdColumn, idWithinFamilyColumn } from './columns'
|
||||
import { SequelizeAttributes } from './types'
|
||||
|
||||
export interface TimelimitRuleAttributes {
|
||||
familyId: string
|
||||
ruleId: string
|
||||
categoryId: string
|
||||
applyToExtraTimeUsage: boolean
|
||||
dayMaskAsBitmask: number
|
||||
maximumTimeInMillis: number
|
||||
}
|
||||
|
||||
export type TimelimitRuleInstance = Sequelize.Instance<TimelimitRuleAttributes> & TimelimitRuleAttributes
|
||||
export type TimelimitRuleModel = Sequelize.Model<TimelimitRuleInstance, TimelimitRuleAttributes>
|
||||
|
||||
export const attributes: SequelizeAttributes<TimelimitRuleAttributes> = {
|
||||
familyId: {
|
||||
...familyIdColumn,
|
||||
primaryKey: true
|
||||
},
|
||||
ruleId: {
|
||||
...idWithinFamilyColumn,
|
||||
primaryKey: true
|
||||
},
|
||||
categoryId: { ...idWithinFamilyColumn },
|
||||
applyToExtraTimeUsage: { ...booleanColumn },
|
||||
dayMaskAsBitmask: {
|
||||
type: Sequelize.TINYINT,
|
||||
allowNull: false,
|
||||
validate: {
|
||||
min: 0,
|
||||
max: 1 | 2 | 4 | 8 | 16 | 32 | 64
|
||||
}
|
||||
},
|
||||
maximumTimeInMillis: {
|
||||
type: Sequelize.INTEGER,
|
||||
allowNull: false,
|
||||
validate: {
|
||||
min: 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const createTimelimitRuleModel = (sequelize: Sequelize.Sequelize): TimelimitRuleModel => sequelize.define<TimelimitRuleInstance, TimelimitRuleAttributes>('TimelimitRule', attributes)
|
||||
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* server component for the TimeLimit App
|
||||
* Copyright (C) 2019 Jonas Lochmann
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import * as Sequelize from 'sequelize'
|
||||
|
||||
export type SequelizeAttributes<T extends { [key: string]: any }> = {
|
||||
[P in keyof T]: Sequelize.DefineAttributeColumnOptions;
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
* server component for the TimeLimit App
|
||||
* Copyright (C) 2019 Jonas Lochmann
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import * as Sequelize from 'sequelize'
|
||||
import { familyIdColumn, idWithinFamilyColumn, timestampColumn } from './columns'
|
||||
import { SequelizeAttributes } from './types'
|
||||
|
||||
export interface UsedTimeAttributesVersion1 {
|
||||
familyId: string
|
||||
categoryId: string
|
||||
dayOfEpoch: number
|
||||
usedTime: number
|
||||
}
|
||||
|
||||
export interface UsedTimeAttributesVersion2 {
|
||||
lastUpdate: string
|
||||
}
|
||||
|
||||
export type UsedTimeAttributes = UsedTimeAttributesVersion1 & UsedTimeAttributesVersion2
|
||||
|
||||
export type UsedTimeInstance = Sequelize.Instance<UsedTimeAttributes> & UsedTimeAttributes
|
||||
export type UsedTimeModel = Sequelize.Model<UsedTimeInstance, UsedTimeAttributes>
|
||||
|
||||
export const attributesVersion1: SequelizeAttributes<UsedTimeAttributesVersion1> = {
|
||||
familyId: {
|
||||
...familyIdColumn,
|
||||
primaryKey: true
|
||||
},
|
||||
categoryId: {
|
||||
...idWithinFamilyColumn,
|
||||
primaryKey: true
|
||||
},
|
||||
dayOfEpoch: {
|
||||
type: Sequelize.INTEGER,
|
||||
allowNull: false,
|
||||
validate: {
|
||||
min: 0
|
||||
},
|
||||
primaryKey: true
|
||||
},
|
||||
usedTime: {
|
||||
type: Sequelize.INTEGER,
|
||||
allowNull: false,
|
||||
validate: {
|
||||
min: 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const attributesVersion2: SequelizeAttributes<UsedTimeAttributesVersion2> = {
|
||||
lastUpdate: {
|
||||
...timestampColumn,
|
||||
defaultValue: 0
|
||||
}
|
||||
}
|
||||
|
||||
export const attributes = {
|
||||
...attributesVersion1,
|
||||
...attributesVersion2
|
||||
}
|
||||
|
||||
export const createUsedTimeModel = (sequelize: Sequelize.Sequelize): UsedTimeModel => sequelize.define<UsedTimeInstance, UsedTimeAttributes>('UsedTime', attributes)
|
||||
@@ -0,0 +1,131 @@
|
||||
/*
|
||||
* server component for the TimeLimit App
|
||||
* Copyright (C) 2019 Jonas Lochmann
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import * as Sequelize from 'sequelize'
|
||||
import { optionalPasswordRegex, optionalSaltRegex } from '../util/password'
|
||||
import { booleanColumn, createEnumColumn, familyIdColumn, idWithinFamilyColumn, labelColumn, optionalIdWithinFamilyColumn, timestampColumn } from './columns'
|
||||
import { SequelizeAttributes } from './types'
|
||||
|
||||
export interface UserAttributesVersion1 {
|
||||
familyId: string
|
||||
userId: string
|
||||
name: string
|
||||
passwordHash: string
|
||||
secondPasswordHash: string
|
||||
secondPasswordSalt: string
|
||||
type: 'parent' | 'child'
|
||||
mail: string
|
||||
timeZone: string
|
||||
disableTimelimitsUntil: string
|
||||
// empty = unset; can contain an invalid device id or the id of an device which is not used by this user
|
||||
// in this case, it should be treated like unset
|
||||
currentDevice: string
|
||||
}
|
||||
|
||||
export interface UserAttributesVersion2 {
|
||||
categoryForNotAssignedApps: string
|
||||
}
|
||||
|
||||
export interface UserAttributesVersion3 {
|
||||
relaxPrimaryDeviceRule: boolean
|
||||
}
|
||||
|
||||
export interface UserAttributesVersion4 {
|
||||
// 1: manipulation warnings
|
||||
mailNotificationFlags: number
|
||||
}
|
||||
|
||||
export type UserAttributes = UserAttributesVersion1 & UserAttributesVersion2 &
|
||||
UserAttributesVersion3 & UserAttributesVersion4
|
||||
|
||||
export type UserInstance = Sequelize.Instance<UserAttributes> & UserAttributes
|
||||
export type UserModel = Sequelize.Model<UserInstance, UserAttributes>
|
||||
|
||||
export const attributesVersion1: SequelizeAttributes<UserAttributesVersion1> = {
|
||||
familyId: {
|
||||
...familyIdColumn,
|
||||
primaryKey: true
|
||||
},
|
||||
userId: {
|
||||
...idWithinFamilyColumn,
|
||||
primaryKey: true
|
||||
},
|
||||
name: { ...labelColumn },
|
||||
passwordHash: {
|
||||
type: Sequelize.STRING,
|
||||
allowNull: false,
|
||||
validate: {
|
||||
is: optionalPasswordRegex
|
||||
}
|
||||
},
|
||||
secondPasswordHash: {
|
||||
type: Sequelize.STRING,
|
||||
allowNull: false,
|
||||
validate: {
|
||||
is: optionalPasswordRegex
|
||||
}
|
||||
},
|
||||
secondPasswordSalt: {
|
||||
type: Sequelize.STRING,
|
||||
allowNull: false,
|
||||
validate: {
|
||||
is: optionalSaltRegex
|
||||
}
|
||||
},
|
||||
type: createEnumColumn(['parent', 'child']),
|
||||
mail: {
|
||||
type: Sequelize.STRING,
|
||||
allowNull: false
|
||||
},
|
||||
timeZone: { ...labelColumn },
|
||||
disableTimelimitsUntil: { ...timestampColumn },
|
||||
currentDevice: { ...optionalIdWithinFamilyColumn }
|
||||
}
|
||||
|
||||
export const attributesVersion2: SequelizeAttributes<UserAttributesVersion2> = {
|
||||
categoryForNotAssignedApps: {
|
||||
...optionalIdWithinFamilyColumn,
|
||||
defaultValue: ''
|
||||
}
|
||||
}
|
||||
|
||||
export const attributesVersion3: SequelizeAttributes<UserAttributesVersion3> = {
|
||||
relaxPrimaryDeviceRule: {
|
||||
...booleanColumn,
|
||||
defaultValue: false
|
||||
}
|
||||
}
|
||||
|
||||
export const attributesVersion4: SequelizeAttributes<UserAttributesVersion4> = {
|
||||
mailNotificationFlags: {
|
||||
type: Sequelize.INTEGER,
|
||||
defaultValue: 0,
|
||||
validate: {
|
||||
min: 0,
|
||||
max: 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const attributes: SequelizeAttributes<UserAttributes> = {
|
||||
...attributesVersion1,
|
||||
...attributesVersion2,
|
||||
...attributesVersion3,
|
||||
...attributesVersion4
|
||||
}
|
||||
|
||||
export const createUserModel = (sequelize: Sequelize.Sequelize): UserModel => sequelize.define<UserInstance, UserAttributes>('User', attributes)
|
||||
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* server component for the TimeLimit App
|
||||
* Copyright (C) 2019 Jonas Lochmann
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { Unauthorized } from 'http-errors'
|
||||
import { Database } from '../../database'
|
||||
import { generateAuthToken } from '../../util/token'
|
||||
|
||||
export const createAuthTokenByMailAddress = async ({ mail, database }: {mail: string, database: Database}) => {
|
||||
const token = generateAuthToken()
|
||||
|
||||
await database.authtoken.create({
|
||||
token,
|
||||
mail,
|
||||
createdAt: Date.now().toString()
|
||||
})
|
||||
|
||||
return token
|
||||
}
|
||||
|
||||
export const getMailByAuthToken = async ({ mailAuthToken, database }: {mailAuthToken: string, database: Database}) => {
|
||||
const entry = await database.authtoken.findOne({
|
||||
where: {
|
||||
token: mailAuthToken
|
||||
}
|
||||
})
|
||||
|
||||
if (entry) {
|
||||
return entry.mail
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
export const requireMailByAuthToken = async ({ mailAuthToken, database }: {mailAuthToken: string, database: Database}) => {
|
||||
const mail = await getMailByAuthToken({ mailAuthToken, database })
|
||||
|
||||
if (!mail) {
|
||||
throw new Unauthorized()
|
||||
}
|
||||
|
||||
return mail
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
/*
|
||||
* server component for the TimeLimit App
|
||||
* Copyright (C) 2019 Jonas Lochmann
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { Forbidden, Gone, InternalServerError, TooManyRequests } from 'http-errors'
|
||||
import { Database } from '../../database'
|
||||
import { sendAuthenticationMail } from '../../util/mail'
|
||||
import { randomWords } from '../../util/random-words'
|
||||
import { checkMailSendLimit } from '../../util/ratelimit-authmail'
|
||||
import { generateAuthToken } from '../../util/token'
|
||||
import { createAuthTokenByMailAddress } from './index'
|
||||
|
||||
export const sendLoginCode = async ({ mail, locale, database }: {
|
||||
mail: string
|
||||
locale: string
|
||||
database: Database
|
||||
}): Promise<{mailLoginToken: string}> => {
|
||||
try {
|
||||
await checkMailSendLimit(mail)
|
||||
} catch (ex) {
|
||||
throw new TooManyRequests()
|
||||
}
|
||||
|
||||
const mailLoginToken = generateAuthToken()
|
||||
const code = randomWords(3)
|
||||
|
||||
await sendAuthenticationMail({
|
||||
receiver: mail,
|
||||
code,
|
||||
locale
|
||||
})
|
||||
|
||||
await database.mailLoginToken.create({
|
||||
mailLoginToken,
|
||||
receivedCode: code,
|
||||
mail,
|
||||
createdAt: Date.now().toString(10),
|
||||
remainingAttempts: 3
|
||||
})
|
||||
|
||||
return {
|
||||
mailLoginToken
|
||||
}
|
||||
}
|
||||
|
||||
// 403 Forbidden = receivedCode is invalid
|
||||
// 410 Gone = mailLoginToken is invalid or expired
|
||||
export const signInByMailCode = async ({ mailLoginToken, receivedCode, database }: {
|
||||
mailLoginToken: string
|
||||
receivedCode: string
|
||||
database: Database
|
||||
}): Promise<{mailAuthToken: string}> => {
|
||||
const { mail, status } = await database.transaction(async (transaction) => {
|
||||
const entry = await database.mailLoginToken.findOne({
|
||||
where: {
|
||||
mailLoginToken
|
||||
},
|
||||
transaction
|
||||
})
|
||||
|
||||
if ((!entry) || entry.remainingAttempts === 0) {
|
||||
return {
|
||||
mail: null,
|
||||
status: 'gone'
|
||||
}
|
||||
}
|
||||
|
||||
if (entry.receivedCode !== receivedCode) {
|
||||
entry.remainingAttempts--
|
||||
|
||||
await entry.save({ transaction })
|
||||
|
||||
if (entry.remainingAttempts === 0) {
|
||||
return {
|
||||
mail: null,
|
||||
status: 'gone'
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
mail: null,
|
||||
status: 'forbidden'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
mail: entry.mail,
|
||||
status: null
|
||||
}
|
||||
})
|
||||
|
||||
if (!mail) {
|
||||
if (status === 'gone') {
|
||||
throw new Gone()
|
||||
} else if (status === 'forbidden') {
|
||||
throw new Forbidden()
|
||||
} else {
|
||||
throw new InternalServerError()
|
||||
}
|
||||
}
|
||||
|
||||
const mailAuthToken = await createAuthTokenByMailAddress({ mail, database })
|
||||
|
||||
return { mailAuthToken }
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
* server component for the TimeLimit App
|
||||
* Copyright (C) 2019 Jonas Lochmann
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { Unauthorized } from 'http-errors'
|
||||
import { RegisterChildDeviceRequest } from '../../api/schema'
|
||||
import { Database } from '../../database'
|
||||
import { generateAuthToken, generateVersionId } from '../../util/token'
|
||||
import { WebsocketApi } from '../../websocket'
|
||||
import { prepareDeviceEntry } from '../device/prepare-device-entry'
|
||||
import { notifyClientsAboutChanges } from '../websocket'
|
||||
|
||||
export const addChildDevice = async ({ database, websocket, request }: {
|
||||
database: Database
|
||||
websocket: WebsocketApi
|
||||
request: RegisterChildDeviceRequest
|
||||
}) => {
|
||||
const { response, familyId } = await database.transaction(async (transaction) => {
|
||||
const entry = await database.addDeviceToken.findOne({
|
||||
where: {
|
||||
token: request.registerToken.toLowerCase()
|
||||
},
|
||||
transaction
|
||||
})
|
||||
|
||||
if (!entry) {
|
||||
throw new Unauthorized()
|
||||
}
|
||||
|
||||
await entry.destroy({ transaction })
|
||||
|
||||
const { deviceId, familyId } = entry
|
||||
const deviceAuthToken = generateAuthToken()
|
||||
|
||||
await database.device.create(prepareDeviceEntry({
|
||||
familyId,
|
||||
deviceId,
|
||||
deviceAuthToken,
|
||||
deviceName: request.deviceName,
|
||||
newDeviceInfo: request.childDevice,
|
||||
userId: ''
|
||||
}), { transaction })
|
||||
|
||||
await database.family.update({
|
||||
deviceListVersion: generateVersionId()
|
||||
}, {
|
||||
where: {
|
||||
familyId
|
||||
},
|
||||
transaction
|
||||
})
|
||||
|
||||
return {
|
||||
response: {
|
||||
deviceId,
|
||||
deviceAuthToken
|
||||
},
|
||||
familyId
|
||||
}
|
||||
})
|
||||
|
||||
await notifyClientsAboutChanges({ familyId, websocket, database, isImportant: true, sourceDeviceId: response.deviceId })
|
||||
|
||||
return response
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
* server component for the TimeLimit App
|
||||
* Copyright (C) 2019 Jonas Lochmann
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { Conflict, Unauthorized } from 'http-errors'
|
||||
import { Database } from '../../database'
|
||||
import { WebsocketApi } from '../../websocket'
|
||||
|
||||
export const logoutAtPrimaryDevice = async ({ deviceAuthToken, database, websocket }: {
|
||||
deviceAuthToken: string
|
||||
database: Database
|
||||
websocket: WebsocketApi
|
||||
}) => {
|
||||
await database.transaction(async (transaction) => {
|
||||
const ownDeviceEntryUnsafe = await database.device.findOne({
|
||||
where: {
|
||||
deviceAuthToken
|
||||
},
|
||||
transaction,
|
||||
attributes: ['familyId', 'currentUserId', 'deviceId']
|
||||
})
|
||||
|
||||
if (!ownDeviceEntryUnsafe) {
|
||||
throw new Unauthorized()
|
||||
}
|
||||
|
||||
const ownDeviceEntry = {
|
||||
familyId: ownDeviceEntryUnsafe.familyId,
|
||||
currentUserId: ownDeviceEntryUnsafe.currentUserId
|
||||
}
|
||||
|
||||
const deviceUserEntryUnsafe = await database.user.findOne({
|
||||
where: {
|
||||
familyId: ownDeviceEntry.familyId,
|
||||
userId: ownDeviceEntry.currentUserId,
|
||||
type: 'child'
|
||||
},
|
||||
attributes: ['currentDevice'],
|
||||
transaction
|
||||
})
|
||||
|
||||
if (!deviceUserEntryUnsafe) {
|
||||
throw new Conflict()
|
||||
}
|
||||
|
||||
const deviceUserEntry = {
|
||||
currentDevice: deviceUserEntryUnsafe.currentDevice
|
||||
}
|
||||
|
||||
const otherDeviceEntryUnsafe = await database.device.findOne({
|
||||
where: {
|
||||
familyId: ownDeviceEntry.familyId,
|
||||
deviceId: deviceUserEntry.currentDevice,
|
||||
currentUserId: ownDeviceEntry.currentUserId
|
||||
},
|
||||
attributes: ['deviceAuthToken'],
|
||||
transaction
|
||||
})
|
||||
|
||||
if (!otherDeviceEntryUnsafe) {
|
||||
throw new Conflict()
|
||||
}
|
||||
|
||||
const otherDeviceEntry = {
|
||||
deviceAuthToken: otherDeviceEntryUnsafe.deviceAuthToken
|
||||
}
|
||||
|
||||
websocket.triggerLogoutByDeviceAuthToken({
|
||||
deviceAuthToken: otherDeviceEntry.deviceAuthToken
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,192 @@
|
||||
/*
|
||||
* server component for the TimeLimit App
|
||||
* Copyright (C) 2019 Jonas Lochmann
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { Conflict, Unauthorized } from 'http-errors'
|
||||
import * as Sequelize from 'sequelize'
|
||||
import { Database } from '../../database'
|
||||
import { generateVersionId } from '../../util/token'
|
||||
import { WebsocketApi } from '../../websocket'
|
||||
import { notifyClientsAboutChanges } from '../websocket'
|
||||
|
||||
export const setPrimaryDevice = async ({ database, websocket, deviceAuthToken, currentUserId, action }: {
|
||||
database: Database
|
||||
websocket: WebsocketApi
|
||||
deviceAuthToken: string
|
||||
currentUserId: string
|
||||
action: 'set this device' | 'unset this device'
|
||||
}): Promise<'assigned to other device' | 'requires full version' | 'success'> => {
|
||||
const response = await database.transaction(async (transaction): Promise<{
|
||||
response: 'assigned to other device' | 'requires full version' | 'success',
|
||||
sourceDeviceId: string,
|
||||
familyId: string
|
||||
}> => {
|
||||
const deviceEntryUnsafe = await database.device.findOne({
|
||||
where: {
|
||||
deviceAuthToken
|
||||
},
|
||||
transaction,
|
||||
attributes: ['familyId', 'currentUserId', 'deviceId']
|
||||
})
|
||||
|
||||
if (!deviceEntryUnsafe) {
|
||||
throw new Unauthorized()
|
||||
}
|
||||
|
||||
const deviceEntry = {
|
||||
familyId: deviceEntryUnsafe.familyId,
|
||||
currentUserId: deviceEntryUnsafe.currentUserId,
|
||||
deviceId: deviceEntryUnsafe.deviceId
|
||||
}
|
||||
|
||||
if ((deviceEntry.currentUserId !== currentUserId) || (currentUserId === '')) {
|
||||
throw new Conflict()
|
||||
}
|
||||
|
||||
const userEntryUnsafe = await database.user.findOne({
|
||||
where: {
|
||||
familyId: deviceEntry.familyId,
|
||||
userId: deviceEntry.currentUserId
|
||||
},
|
||||
transaction,
|
||||
lock: Sequelize.Transaction.LOCK.UPDATE,
|
||||
attributes: ['currentDevice']
|
||||
})
|
||||
|
||||
if (!userEntryUnsafe) {
|
||||
throw new Conflict()
|
||||
}
|
||||
|
||||
const userEntry = {
|
||||
currentDevice: userEntryUnsafe.currentDevice
|
||||
}
|
||||
|
||||
const userDeviceEntriesUnsafe = await database.device.findAll({
|
||||
where: {
|
||||
familyId: deviceEntry.familyId,
|
||||
currentUserId
|
||||
},
|
||||
transaction,
|
||||
attributes: ['deviceId']
|
||||
})
|
||||
|
||||
const userDeviceEntries = userDeviceEntriesUnsafe.map((item) => ({
|
||||
deviceId: item.deviceId
|
||||
}))
|
||||
|
||||
if (userDeviceEntries.length >= 2) {
|
||||
const familyEntryUnsafe = await database.family.findOne({
|
||||
where: {
|
||||
familyId: deviceEntry.familyId
|
||||
},
|
||||
transaction,
|
||||
attributes: ['hasFullVersion']
|
||||
})
|
||||
|
||||
if (!familyEntryUnsafe) {
|
||||
throw new Conflict()
|
||||
}
|
||||
|
||||
const familyEntry = {
|
||||
hasFullVersion: familyEntryUnsafe.hasFullVersion
|
||||
}
|
||||
|
||||
if (!familyEntry.hasFullVersion) {
|
||||
return {
|
||||
response: 'requires full version',
|
||||
sourceDeviceId: deviceEntry.deviceId,
|
||||
familyId: deviceEntry.familyId
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (action === 'set this device') {
|
||||
// check that no other device is selected
|
||||
if (userDeviceEntries.find((item) => item.deviceId === userEntry.currentDevice)) {
|
||||
return {
|
||||
response: 'assigned to other device',
|
||||
sourceDeviceId: deviceEntry.deviceId,
|
||||
familyId: deviceEntry.familyId
|
||||
}
|
||||
}
|
||||
|
||||
// update
|
||||
const [affectedRows] = await database.user.update({
|
||||
currentDevice: deviceEntry.deviceId
|
||||
}, {
|
||||
where: {
|
||||
familyId: deviceEntry.familyId,
|
||||
userId: currentUserId
|
||||
},
|
||||
transaction
|
||||
})
|
||||
|
||||
if (affectedRows !== 1) {
|
||||
throw new Conflict()
|
||||
}
|
||||
} else if (action === 'unset this device') {
|
||||
if (userEntry.currentDevice !== deviceEntry.deviceId) {
|
||||
throw new Conflict()
|
||||
}
|
||||
|
||||
// update
|
||||
const [affectedRows] = await database.user.update({
|
||||
currentDevice: ''
|
||||
}, {
|
||||
where: {
|
||||
familyId: deviceEntry.familyId,
|
||||
userId: currentUserId
|
||||
},
|
||||
transaction
|
||||
})
|
||||
|
||||
if (affectedRows !== 1) {
|
||||
throw new Conflict()
|
||||
}
|
||||
} else {
|
||||
throw new Error('illegal state')
|
||||
}
|
||||
|
||||
// invalidiate user list
|
||||
await database.family.update({
|
||||
userListVersion: generateVersionId()
|
||||
}, {
|
||||
transaction,
|
||||
where: {
|
||||
familyId: deviceEntry.familyId
|
||||
}
|
||||
})
|
||||
|
||||
return {
|
||||
response: 'success',
|
||||
sourceDeviceId: deviceEntry.deviceId,
|
||||
familyId: deviceEntry.familyId
|
||||
}
|
||||
})
|
||||
|
||||
if (response.response === 'success') {
|
||||
// trigger sync
|
||||
await notifyClientsAboutChanges({
|
||||
familyId: response.familyId,
|
||||
sourceDeviceId: response.sourceDeviceId,
|
||||
websocket,
|
||||
database,
|
||||
isImportant: false // the source device knows it already
|
||||
})
|
||||
}
|
||||
|
||||
return response.response
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* server component for the TimeLimit App
|
||||
* Copyright (C) 2019 Jonas Lochmann
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { NewDeviceInfo } from '../../api/schema'
|
||||
import { DeviceAttributes } from '../../database/device'
|
||||
import { generateVersionId } from '../../util/token'
|
||||
|
||||
export const prepareDeviceEntry = ({ familyId, userId, deviceAuthToken, deviceId, deviceName, newDeviceInfo }: {
|
||||
familyId: string
|
||||
userId: string
|
||||
deviceAuthToken: string
|
||||
deviceId: string
|
||||
deviceName: string
|
||||
newDeviceInfo: NewDeviceInfo
|
||||
}): DeviceAttributes => ({
|
||||
familyId,
|
||||
deviceId,
|
||||
currentUserId: userId,
|
||||
installedAppsVersion: generateVersionId(),
|
||||
name: deviceName,
|
||||
model: newDeviceInfo.model,
|
||||
addedAt: Date.now().toString(10),
|
||||
deviceAuthToken,
|
||||
networkTime: 'disabled',
|
||||
nextSequenceNumber: 0,
|
||||
currentProtectionLevel: 'none',
|
||||
highestProtectionLevel: 'none',
|
||||
currentUsageStatsPermission: 'not granted',
|
||||
highestUsageStatsPermission: 'not granted',
|
||||
currentNotificationAccessPermission: 'not granted',
|
||||
highestNotificationAccessPermission: 'not granted',
|
||||
currentAppVersion: 0,
|
||||
highestAppVersion: 0,
|
||||
triedDisablingDeviceAdmin: false,
|
||||
didReboot: false,
|
||||
hadManipulation: false,
|
||||
lastConnectivity: '0',
|
||||
notSeenForLongTime: false,
|
||||
didDeviceReportUninstall: false,
|
||||
isUserKeptSignedIn: false,
|
||||
showDeviceConnected: false,
|
||||
defaultUserId: '',
|
||||
defaultUserTimeout: 0,
|
||||
considerRebootManipulation: false
|
||||
})
|
||||
@@ -0,0 +1,109 @@
|
||||
/*
|
||||
* server component for the TimeLimit App
|
||||
* Copyright (C) 2019 Jonas Lochmann
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { Conflict } from 'http-errors'
|
||||
import { Database } from '../../database'
|
||||
import { generateVersionId } from '../../util/token'
|
||||
import { WebsocketApi } from '../../websocket'
|
||||
import { notifyClientsAboutChanges } from '../websocket'
|
||||
|
||||
export async function removeDevice ({ database, familyId, deviceId, websocket }: {
|
||||
database: Database
|
||||
familyId: string
|
||||
deviceId: string
|
||||
websocket: WebsocketApi
|
||||
}) {
|
||||
const { oldDeviceAuthToken } = await database.transaction(async (transaction) => {
|
||||
const deviceEntry = await database.device.findOne({
|
||||
where: {
|
||||
familyId,
|
||||
deviceId
|
||||
},
|
||||
transaction
|
||||
})
|
||||
|
||||
if (!deviceEntry) {
|
||||
throw new Conflict()
|
||||
}
|
||||
|
||||
// remove app entries
|
||||
await database.app.destroy({
|
||||
where: {
|
||||
familyId,
|
||||
deviceId
|
||||
},
|
||||
transaction
|
||||
})
|
||||
|
||||
// remove as current device
|
||||
await database.user.update({
|
||||
currentDevice: ''
|
||||
}, {
|
||||
where: {
|
||||
familyId,
|
||||
currentDevice: deviceId
|
||||
},
|
||||
transaction
|
||||
})
|
||||
|
||||
// add to old devices if it is not yet there (it could be there if it reported a uninstall)
|
||||
const oldOldDeviceEntry = await database.oldDevice.findOne({
|
||||
where: {
|
||||
deviceAuthToken: deviceEntry.deviceAuthToken
|
||||
},
|
||||
transaction
|
||||
})
|
||||
|
||||
if (!oldOldDeviceEntry) {
|
||||
await database.oldDevice.create({
|
||||
deviceAuthToken: deviceEntry.deviceAuthToken
|
||||
}, {
|
||||
transaction
|
||||
})
|
||||
}
|
||||
|
||||
// remove from the device list
|
||||
await deviceEntry.destroy({ transaction })
|
||||
|
||||
// invalidiate the caches
|
||||
await database.family.update({
|
||||
deviceListVersion: generateVersionId(),
|
||||
// the device could have become unassigned during this
|
||||
userListVersion: generateVersionId()
|
||||
}, {
|
||||
where: {
|
||||
familyId: deviceEntry.familyId
|
||||
},
|
||||
transaction
|
||||
})
|
||||
|
||||
return { oldDeviceAuthToken: deviceEntry.deviceAuthToken }
|
||||
})
|
||||
|
||||
await notifyClientsAboutChanges({
|
||||
database,
|
||||
websocket,
|
||||
familyId,
|
||||
sourceDeviceId: null,
|
||||
isImportant: false
|
||||
})
|
||||
|
||||
websocket.triggerSyncByDeviceAuthToken({
|
||||
deviceAuthToken: oldDeviceAuthToken,
|
||||
isImportant: true
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
/*
|
||||
* server component for the TimeLimit App
|
||||
* Copyright (C) 2019 Jonas Lochmann
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { Database } from '../../database'
|
||||
import { generateAuthToken, generateVersionId } from '../../util/token'
|
||||
import { WebsocketApi } from '../../websocket'
|
||||
import { sendUninstallWarnings } from '../warningmail/uninstall'
|
||||
import { notifyClientsAboutChanges } from '../websocket'
|
||||
|
||||
export async function reportDeviceRemoved ({ database, deviceAuthToken, websocket }: {
|
||||
database: Database
|
||||
deviceAuthToken: string
|
||||
websocket: WebsocketApi
|
||||
}) {
|
||||
const result = await database.transaction(async (transaction) => {
|
||||
const deviceEntry = await database.device.findOne({
|
||||
where: {
|
||||
deviceAuthToken
|
||||
},
|
||||
transaction
|
||||
})
|
||||
|
||||
if (deviceEntry) {
|
||||
deviceEntry.didDeviceReportUninstall = true
|
||||
deviceEntry.deviceAuthToken = generateAuthToken() // invalidiate the token
|
||||
deviceEntry.save({ transaction })
|
||||
|
||||
// invalidiate device list
|
||||
await database.family.update({
|
||||
deviceListVersion: generateVersionId()
|
||||
}, {
|
||||
where: {
|
||||
familyId: deviceEntry.familyId
|
||||
},
|
||||
transaction
|
||||
})
|
||||
|
||||
// add to old devices
|
||||
await database.oldDevice.create({
|
||||
deviceAuthToken: deviceEntry.deviceAuthToken
|
||||
}, {
|
||||
transaction
|
||||
})
|
||||
|
||||
return { familyId: deviceEntry.familyId, deviceName: deviceEntry.name }
|
||||
} else {
|
||||
const oldDeviceEntry = await database.oldDevice.findOne({
|
||||
where: {
|
||||
deviceAuthToken
|
||||
},
|
||||
transaction
|
||||
})
|
||||
|
||||
if (!oldDeviceEntry) {
|
||||
throw new Error('device not found')
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
})
|
||||
|
||||
if (result) {
|
||||
await notifyClientsAboutChanges({
|
||||
database,
|
||||
websocket,
|
||||
familyId: result.familyId,
|
||||
sourceDeviceId: null,
|
||||
isImportant: false
|
||||
})
|
||||
|
||||
await sendUninstallWarnings({
|
||||
database,
|
||||
familyId: result.familyId,
|
||||
deviceName: result.deviceName
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* server component for the TimeLimit App
|
||||
* Copyright (C) 2019 Jonas Lochmann
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { Database } from '../../database'
|
||||
import { requireMailByAuthToken } from '../authentication'
|
||||
|
||||
export const canRecoverPassword = async ({ database, mailAuthToken, parentUserId }: {
|
||||
database: Database
|
||||
mailAuthToken: string
|
||||
parentUserId: string
|
||||
}) => {
|
||||
const mail = await requireMailByAuthToken({ mailAuthToken, database })
|
||||
|
||||
const entry = await database.user.findOne({
|
||||
where: {
|
||||
mail,
|
||||
userId: parentUserId,
|
||||
type: 'parent'
|
||||
}
|
||||
})
|
||||
|
||||
return !!entry
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* server component for the TimeLimit App
|
||||
* Copyright (C) 2019 Jonas Lochmann
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { Database } from '../../database'
|
||||
import { randomWords } from '../../util/random-words'
|
||||
import { generateIdWithinFamily } from '../../util/token'
|
||||
|
||||
export const createAddDeviceToken = async ({ familyId, database }: {
|
||||
familyId: string
|
||||
database: Database
|
||||
}) => {
|
||||
const token = randomWords(5)
|
||||
const deviceId = generateIdWithinFamily()
|
||||
|
||||
await database.addDeviceToken.destroy({
|
||||
where: {
|
||||
familyId
|
||||
}
|
||||
})
|
||||
|
||||
await database.addDeviceToken.create({
|
||||
familyId,
|
||||
token: token.toLowerCase(),
|
||||
deviceId,
|
||||
createdAt: Date.now().toString()
|
||||
})
|
||||
|
||||
return { token, deviceId }
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
/*
|
||||
* server component for the TimeLimit App
|
||||
* Copyright (C) 2019 Jonas Lochmann
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { Conflict } from 'http-errors'
|
||||
import { NewDeviceInfo, ParentPassword } from '../../api/schema'
|
||||
import { Database } from '../../database'
|
||||
import {
|
||||
generateAuthToken, generateFamilyId, generateIdWithinFamily, generateVersionId
|
||||
} from '../../util/token'
|
||||
import { requireMailByAuthToken } from '../authentication'
|
||||
import { prepareDeviceEntry } from '../device/prepare-device-entry'
|
||||
|
||||
export const createFamily = async ({ database, mailAuthToken, firstParentDevice, password, timeZone, parentName, deviceName }: {
|
||||
database: Database,
|
||||
mailAuthToken: string,
|
||||
firstParentDevice: NewDeviceInfo,
|
||||
password: ParentPassword,
|
||||
timeZone: string,
|
||||
parentName: string,
|
||||
deviceName: string
|
||||
}) => {
|
||||
const now = Date.now().toString(10)
|
||||
const mail = await requireMailByAuthToken({ database, mailAuthToken })
|
||||
|
||||
return database.transaction(async (transaction) => {
|
||||
// ensure that no family was created for this mail yet
|
||||
const exisitngUserEntry = await database.user.findOne({
|
||||
where: {
|
||||
mail
|
||||
},
|
||||
transaction
|
||||
})
|
||||
|
||||
if (exisitngUserEntry) {
|
||||
throw new Conflict()
|
||||
}
|
||||
|
||||
const familyId = generateFamilyId()
|
||||
const userId = generateIdWithinFamily()
|
||||
const deviceId = generateIdWithinFamily()
|
||||
const deviceAuthToken = generateAuthToken()
|
||||
|
||||
// create family
|
||||
await database.family.create({
|
||||
familyId,
|
||||
name: '',
|
||||
createdAt: now,
|
||||
userListVersion: generateVersionId(),
|
||||
deviceListVersion: generateVersionId(),
|
||||
// 14 days demo version
|
||||
fullVersionUntil: (Date.now() + 1000 * 60 * 60 * 24 * 14).toString(10),
|
||||
hasFullVersion: true
|
||||
}, { transaction })
|
||||
|
||||
// create parent user
|
||||
await database.user.create({
|
||||
familyId,
|
||||
userId,
|
||||
name: parentName,
|
||||
passwordHash: password.hash,
|
||||
secondPasswordHash: password.secondHash,
|
||||
secondPasswordSalt: password.secondSalt,
|
||||
type: 'parent',
|
||||
mail,
|
||||
timeZone,
|
||||
disableTimelimitsUntil: '0',
|
||||
currentDevice: '',
|
||||
categoryForNotAssignedApps: '',
|
||||
relaxPrimaryDeviceRule: false,
|
||||
mailNotificationFlags: 1 // enable warning notifications
|
||||
}, { transaction })
|
||||
|
||||
// add parent device
|
||||
await database.device.create(prepareDeviceEntry({
|
||||
familyId,
|
||||
deviceId,
|
||||
deviceName,
|
||||
newDeviceInfo: firstParentDevice,
|
||||
userId,
|
||||
deviceAuthToken
|
||||
}), { transaction })
|
||||
|
||||
return {
|
||||
deviceAuthToken,
|
||||
deviceId
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* server component for the TimeLimit App
|
||||
* Copyright (C) 2019 Jonas Lochmann
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { Database } from '../../database'
|
||||
import { requireMailByAuthToken } from '../authentication'
|
||||
|
||||
const getStatusByMailAddress = async ({ mail, database }: {mail: string, database: Database}) => {
|
||||
if (!mail) {
|
||||
throw new Error('no mail address')
|
||||
}
|
||||
|
||||
const entry = await database.user.findOne({
|
||||
where: {
|
||||
mail
|
||||
}
|
||||
})
|
||||
|
||||
if (entry) {
|
||||
return 'with family'
|
||||
} else {
|
||||
return 'without family'
|
||||
}
|
||||
}
|
||||
|
||||
export const getStatusByMailToken = async ({ mailAuthToken, database }: {mailAuthToken: string, database: Database}) => {
|
||||
const mail = await requireMailByAuthToken({ mailAuthToken, database })
|
||||
const status = await getStatusByMailAddress({ mail, database })
|
||||
|
||||
return { mail, status }
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
/*
|
||||
* server component for the TimeLimit App
|
||||
* Copyright (C) 2019 Jonas Lochmann
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { Conflict, Unauthorized } from 'http-errors'
|
||||
import * as Sequelize from 'sequelize'
|
||||
import { Database } from '../../database'
|
||||
import { generateVersionId } from '../../util/token'
|
||||
import { WebsocketApi } from '../../websocket'
|
||||
import { requireMailByAuthToken } from '../authentication'
|
||||
import { notifyClientsAboutChanges } from '../websocket'
|
||||
|
||||
export const linkMailAddress = async ({ mailAuthToken, deviceAuthToken, parentUserId, parentPasswordSecondHash, database, websocket }: {
|
||||
mailAuthToken: string
|
||||
deviceAuthToken: string
|
||||
parentUserId: string
|
||||
parentPasswordSecondHash: string
|
||||
database: Database
|
||||
websocket: WebsocketApi
|
||||
}) => {
|
||||
const deviceEntry = await database.device.findOne({
|
||||
where: {
|
||||
deviceAuthToken
|
||||
}
|
||||
})
|
||||
|
||||
if (!deviceEntry) {
|
||||
throw new Unauthorized()
|
||||
}
|
||||
|
||||
const familyId = deviceEntry.familyId
|
||||
|
||||
const mailAddress = await requireMailByAuthToken({ mailAuthToken, database })
|
||||
|
||||
const exisitingUser = await database.user.findOne({
|
||||
where: {
|
||||
mail: mailAddress
|
||||
}
|
||||
})
|
||||
|
||||
if (exisitingUser) {
|
||||
throw new Conflict()
|
||||
}
|
||||
|
||||
await database.transaction(async (transaction) => {
|
||||
const parentEntry = await database.user.findOne({
|
||||
where: {
|
||||
type: 'parent',
|
||||
familyId,
|
||||
userId: parentUserId
|
||||
},
|
||||
transaction,
|
||||
lock: Sequelize.Transaction.LOCK.UPDATE
|
||||
})
|
||||
|
||||
if (!parentEntry) {
|
||||
throw new Conflict()
|
||||
}
|
||||
|
||||
if (parentEntry.mail !== '') {
|
||||
throw new Conflict()
|
||||
}
|
||||
|
||||
if (parentEntry.secondPasswordHash !== parentPasswordSecondHash) {
|
||||
throw new Conflict()
|
||||
}
|
||||
|
||||
if (!parentEntry.secondPasswordSalt) {
|
||||
throw new Conflict()
|
||||
}
|
||||
|
||||
parentEntry.mail = mailAddress
|
||||
|
||||
await parentEntry.save({ transaction })
|
||||
|
||||
// invalidiate client caches
|
||||
await database.family.update({
|
||||
userListVersion: generateVersionId()
|
||||
}, {
|
||||
where: {
|
||||
familyId
|
||||
},
|
||||
transaction
|
||||
})
|
||||
})
|
||||
|
||||
await notifyClientsAboutChanges({
|
||||
familyId,
|
||||
sourceDeviceId: null,
|
||||
database,
|
||||
websocket,
|
||||
isImportant: true
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
* server component for the TimeLimit App
|
||||
* Copyright (C) 2019 Jonas Lochmann
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { Conflict } from 'http-errors'
|
||||
import * as Sequelize from 'sequelize'
|
||||
import { ParentPassword } from '../../api/schema'
|
||||
import { Database } from '../../database'
|
||||
import { generateVersionId } from '../../util/token'
|
||||
import { WebsocketApi } from '../../websocket'
|
||||
import { requireMailByAuthToken } from '../authentication'
|
||||
import { notifyClientsAboutChanges } from '../websocket'
|
||||
|
||||
export const recoverParentPassword = async ({ database, websocket, password, mailAuthToken }: {
|
||||
database: Database
|
||||
websocket: WebsocketApi
|
||||
password: ParentPassword
|
||||
mailAuthToken: string
|
||||
}) => {
|
||||
const mail = await requireMailByAuthToken({ mailAuthToken, database })
|
||||
|
||||
const { familyId } = await database.transaction(async (transaction) => {
|
||||
// update the user entry
|
||||
const userEntry = await database.user.findOne({
|
||||
where: {
|
||||
mail
|
||||
},
|
||||
transaction,
|
||||
lock: Sequelize.Transaction.LOCK.UPDATE
|
||||
})
|
||||
|
||||
if (!userEntry) {
|
||||
return { familyId: null }
|
||||
}
|
||||
|
||||
userEntry.passwordHash = password.hash
|
||||
userEntry.secondPasswordHash = password.secondHash
|
||||
userEntry.secondPasswordSalt = password.secondSalt
|
||||
|
||||
await userEntry.save({ transaction })
|
||||
|
||||
// invalidate the user list
|
||||
await database.family.update({
|
||||
userListVersion: generateVersionId()
|
||||
}, {
|
||||
where: {
|
||||
familyId: userEntry.familyId
|
||||
},
|
||||
transaction
|
||||
})
|
||||
|
||||
return { familyId: userEntry.familyId }
|
||||
})
|
||||
|
||||
if (familyId === null) {
|
||||
throw new Conflict()
|
||||
}
|
||||
|
||||
await notifyClientsAboutChanges({
|
||||
database,
|
||||
familyId,
|
||||
websocket,
|
||||
isImportant: true,
|
||||
sourceDeviceId: null
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
/*
|
||||
* server component for the TimeLimit App
|
||||
* Copyright (C) 2019 Jonas Lochmann
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { Conflict } from 'http-errors'
|
||||
import { NewDeviceInfo } from '../../api/schema'
|
||||
import { Database } from '../../database'
|
||||
import { generateAuthToken, generateIdWithinFamily, generateVersionId } from '../../util/token'
|
||||
import { WebsocketApi } from '../../websocket'
|
||||
import { requireMailByAuthToken } from '../authentication'
|
||||
import { prepareDeviceEntry } from '../device/prepare-device-entry'
|
||||
import { notifyClientsAboutChanges } from '../websocket'
|
||||
|
||||
export const signInIntoFamily = async ({ database, mailAuthToken, newDeviceInfo, deviceName, websocket }: {
|
||||
database: Database
|
||||
mailAuthToken: string
|
||||
newDeviceInfo: NewDeviceInfo
|
||||
deviceName: string
|
||||
websocket: WebsocketApi
|
||||
}) => {
|
||||
const mail = await requireMailByAuthToken({ database, mailAuthToken })
|
||||
|
||||
const { response, familyId, sourceDeviceId } = await database.transaction(async (transaction) => {
|
||||
const userEntryUnsafe = await database.user.findOne({
|
||||
where: {
|
||||
mail
|
||||
},
|
||||
attributes: ['familyId', 'userId'],
|
||||
transaction
|
||||
})
|
||||
|
||||
if (!userEntryUnsafe) {
|
||||
throw new Conflict()
|
||||
}
|
||||
|
||||
const userEntry = {
|
||||
familyId: userEntryUnsafe.familyId,
|
||||
userId: userEntryUnsafe.userId,
|
||||
transaction
|
||||
}
|
||||
|
||||
const deviceAuthToken = generateAuthToken()
|
||||
const deviceId = generateIdWithinFamily()
|
||||
|
||||
await database.device.create(prepareDeviceEntry({
|
||||
familyId: userEntry.familyId,
|
||||
deviceId,
|
||||
userId: userEntry.userId,
|
||||
deviceName,
|
||||
deviceAuthToken,
|
||||
newDeviceInfo
|
||||
}), { transaction })
|
||||
|
||||
// notify about changes
|
||||
await database.family.update({
|
||||
deviceListVersion: generateVersionId()
|
||||
}, {
|
||||
where: {
|
||||
familyId: userEntry.familyId
|
||||
},
|
||||
transaction
|
||||
})
|
||||
|
||||
return {
|
||||
response: {
|
||||
deviceId,
|
||||
deviceAuthToken
|
||||
},
|
||||
sourceDeviceId: deviceId,
|
||||
familyId: userEntry.familyId
|
||||
}
|
||||
})
|
||||
|
||||
await notifyClientsAboutChanges({
|
||||
familyId,
|
||||
websocket,
|
||||
database,
|
||||
isImportant: true,
|
||||
sourceDeviceId
|
||||
})
|
||||
|
||||
return response
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
/*
|
||||
* server component for the TimeLimit App
|
||||
* Copyright (C) 2019 Jonas Lochmann
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { Conflict } from 'http-errors'
|
||||
import * as Sequelize from 'sequelize'
|
||||
import { Database } from '../../database'
|
||||
import { notifyClientsAboutChanges } from '../../function/websocket'
|
||||
import { WebsocketApi } from '../../websocket'
|
||||
|
||||
const day = 1000 * 60 * 60 * 24
|
||||
const month = day * 31
|
||||
const year = day * 366
|
||||
|
||||
export const addPurchase = async ({ database, familyId, type, transactionId, websocket }: {
|
||||
database: Database
|
||||
familyId: string
|
||||
type: 'month' | 'year'
|
||||
transactionId: string
|
||||
websocket: WebsocketApi
|
||||
}) => {
|
||||
const service = 'googleplay'
|
||||
|
||||
await database.transaction(async (transaction) => {
|
||||
const oldPurchaseEntry = await database.purchase.findOne({
|
||||
where: {
|
||||
service,
|
||||
transactionId
|
||||
},
|
||||
transaction
|
||||
})
|
||||
|
||||
if (oldPurchaseEntry) {
|
||||
return
|
||||
}
|
||||
|
||||
const familyEntry = await database.family.findOne({
|
||||
where: {
|
||||
familyId
|
||||
},
|
||||
transaction,
|
||||
lock: Sequelize.Transaction.LOCK.UPDATE
|
||||
})
|
||||
|
||||
if (!familyEntry) {
|
||||
throw new Conflict()
|
||||
}
|
||||
|
||||
const previousFullVersionEndTime = familyEntry.fullVersionUntil
|
||||
|
||||
const newFullVersionUntil = Math.max(parseInt(familyEntry.fullVersionUntil, 10), Date.now()) + (type === 'year' ? year : month)
|
||||
|
||||
familyEntry.fullVersionUntil = newFullVersionUntil.toString(10)
|
||||
familyEntry.hasFullVersion = true
|
||||
|
||||
await familyEntry.save({ transaction })
|
||||
|
||||
await database.purchase.create({
|
||||
familyId,
|
||||
service,
|
||||
transactionId,
|
||||
type,
|
||||
loggedAt: Date.now().toString(10),
|
||||
previousFullVersionEndTime,
|
||||
newFullVersionEndTime: newFullVersionUntil.toString(10)
|
||||
}, {
|
||||
transaction
|
||||
})
|
||||
|
||||
await notifyClientsAboutChanges({
|
||||
familyId,
|
||||
sourceDeviceId: null,
|
||||
database,
|
||||
websocket,
|
||||
isImportant: true
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* server component for the TimeLimit App
|
||||
* Copyright (C) 2019 Jonas Lochmann
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
export const canDoNextPurchase = ({ fullVersionUntil }: {fullVersionUntil: number}) => (
|
||||
fullVersionUntil < (Date.now() + 1000 * 60 * 60 * 24 * 31) // 31 days
|
||||
)
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user