mirror of
https://codeberg.org/timelimit/timelimit-server.git
synced 2026-08-31 19:03:45 +02:00
130 lines
4.6 KiB
TypeScript
130 lines
4.6 KiB
TypeScript
/*
|
|
* server component for the TimeLimit App
|
|
* Copyright (C) 2019 - 2020 Jonas Lochmann
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU Affero General Public License as
|
|
* published by the Free Software Foundation, version 3 of the License.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU Affero General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Affero General Public License
|
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
import { createDecipheriv, createHash } from 'crypto'
|
|
import { ParentAction } from './basetypes'
|
|
import { InvalidActionParameterException } from './meta/exception'
|
|
import { assertHexString, assertIdWithinFamily } from './meta/util'
|
|
|
|
const actionType = 'ChangeParentPasswordAction'
|
|
|
|
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({ actionType, field: 'parentUserId', value: parentUserId })
|
|
|
|
if (
|
|
(!parentUserId) ||
|
|
(!newPasswordFirstHash) ||
|
|
(!newPasswordSecondSalt) ||
|
|
(!newPasswordSecondHashEncrypted) ||
|
|
(!integrity)
|
|
) {
|
|
throw new InvalidActionParameterException({
|
|
actionType,
|
|
staticMessage: 'missing required parameter for change parent password'
|
|
})
|
|
}
|
|
|
|
if (integrity.length !== 128) {
|
|
throw new InvalidActionParameterException({
|
|
actionType,
|
|
staticMessage: 'wrong length of integrity data'
|
|
})
|
|
}
|
|
|
|
assertHexString({ actionType, field: 'newPasswordSecondHashEncrypted', value: newPasswordSecondHashEncrypted })
|
|
assertHexString({ actionType, field: 'integrity', value: integrity })
|
|
|
|
if (newPasswordSecondHashEncrypted.length <= 70) {
|
|
throw new InvalidActionParameterException({ actionType, staticMessage: 'wrong length of the new password' })
|
|
}
|
|
|
|
this.parentUserId = parentUserId
|
|
this.newPasswordFirstHash = newPasswordFirstHash
|
|
this.newPasswordSecondSalt = newPasswordSecondSalt
|
|
this.newPasswordSecondHashEncrypted = newPasswordSecondHashEncrypted
|
|
this.integrity = 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 InvalidChangeParentPasswordIntegrityException()
|
|
}
|
|
}
|
|
|
|
decryptSecondHash ({ oldPasswordSecondHash }: { oldPasswordSecondHash: string }) {
|
|
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
|
|
}
|
|
|
|
export class InvalidChangeParentPasswordIntegrityException extends Error {
|
|
constructor () { super('invalid integrity for change parent password action') }
|
|
}
|