mirror of
https://codeberg.org/timelimit/timelimit-server.git
synced 2026-08-31 19:03:45 +02:00
Add U2F support
This commit is contained in:
@@ -16,6 +16,7 @@
|
||||
*/
|
||||
|
||||
import { json } from 'body-parser'
|
||||
import { createHmac } from 'crypto'
|
||||
import { Router } from 'express'
|
||||
import { BadRequest, Forbidden, Unauthorized } from 'http-errors'
|
||||
import { config } from '../config'
|
||||
@@ -27,6 +28,7 @@ import { getStatusByMailToken } from '../function/parent/get-status-by-mail-addr
|
||||
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 { validateU2fIntegrity, U2fValidationError } from '../function/u2f'
|
||||
import { createIdentityToken, MissingSignSecretException } from '../util/identity-token'
|
||||
import { WebsocketApi } from '../websocket'
|
||||
import {
|
||||
@@ -168,6 +170,56 @@ export const createParentRouter = ({ database, websocket }: {database: Database,
|
||||
}
|
||||
|
||||
return { deviceEntry, parentEntry }
|
||||
} else if (secondPasswordHash.startsWith('u2f:')) {
|
||||
try {
|
||||
const familyEntryUnsafe = await database.family.findOne({
|
||||
where: {
|
||||
familyId: deviceEntry.familyId
|
||||
},
|
||||
transaction,
|
||||
attributes: ['hasFullVersion']
|
||||
})
|
||||
|
||||
if (!familyEntryUnsafe) {
|
||||
throw new Unauthorized()
|
||||
}
|
||||
|
||||
const familyEntry = { hasFullVersion: familyEntryUnsafe.hasFullVersion }
|
||||
|
||||
const hasFullVersion = familyEntry.hasFullVersion || config.alwaysPro
|
||||
|
||||
const u2fResult = await validateU2fIntegrity({
|
||||
integrity: secondPasswordHash,
|
||||
hasFullVersion,
|
||||
familyId: deviceEntry.familyId,
|
||||
deviceId: deviceEntry.deviceId,
|
||||
database,
|
||||
transaction,
|
||||
calculateHmac: (secret) => createHmac('sha256', secret)
|
||||
.update('direct action')
|
||||
.digest()
|
||||
})
|
||||
|
||||
if (u2fResult.userId !== parentId) throw new Unauthorized()
|
||||
|
||||
const parentEntry = await database.user.findOne({
|
||||
where: {
|
||||
familyId: deviceEntry.familyId,
|
||||
type: 'parent',
|
||||
userId: u2fResult.userId
|
||||
},
|
||||
transaction
|
||||
})
|
||||
|
||||
if (!parentEntry) {
|
||||
throw new Unauthorized()
|
||||
}
|
||||
|
||||
return { deviceEntry, parentEntry }
|
||||
} catch (ex) {
|
||||
if (ex instanceof U2fValidationError) throw new Unauthorized()
|
||||
else throw ex
|
||||
}
|
||||
} else {
|
||||
const parentEntry = await database.user.findOne({
|
||||
where: {
|
||||
|
||||
@@ -132,6 +132,7 @@ export const createSyncRouter = ({ database, websocket, connectedDevicesManager,
|
||||
if (serverStatus.krq) { eventHandler.countEvent('pullStatusRequest pendingKeyRequests') }
|
||||
if (serverStatus.kr) { eventHandler.countEvent('pullStatusRequest keyResponses') }
|
||||
if (serverStatus.dh) { eventHandler.countEvent('pullStatusRequest dh') }
|
||||
if (serverStatus.u2f) { eventHandler.countEvent('pullStatusRequest u2f') }
|
||||
|
||||
res.json(serverStatus)
|
||||
} catch (ex) {
|
||||
|
||||
@@ -75,6 +75,9 @@ const definitions = {
|
||||
},
|
||||
"dh": {
|
||||
"type": "string"
|
||||
},
|
||||
"u2f": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
@@ -209,6 +212,29 @@ const definitions = {
|
||||
"type"
|
||||
]
|
||||
},
|
||||
"SerializedAddParentU2fKeyAction": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"ADD_PARENT_U2F"
|
||||
]
|
||||
},
|
||||
"keyHandle": {
|
||||
"type": "string"
|
||||
},
|
||||
"publicKey": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"keyHandle",
|
||||
"publicKey",
|
||||
"type"
|
||||
]
|
||||
},
|
||||
"SerializedAddUserAction": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -535,6 +561,21 @@ const definitions = {
|
||||
"type"
|
||||
]
|
||||
},
|
||||
"SerializedReportU2fLoginAction": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"REPORT_U2F_LOGIN"
|
||||
]
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"type"
|
||||
]
|
||||
},
|
||||
"SerializedRemoveCategoryAppsAction": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -561,6 +602,29 @@ const definitions = {
|
||||
"type"
|
||||
]
|
||||
},
|
||||
"SerializedRemoveParentU2fKeyAction": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"REMOVE_PARENT_U2F"
|
||||
]
|
||||
},
|
||||
"keyHandle": {
|
||||
"type": "string"
|
||||
},
|
||||
"publicKey": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"keyHandle",
|
||||
"publicKey",
|
||||
"type"
|
||||
]
|
||||
},
|
||||
"SerializedRemoveUserAction": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -2747,6 +2811,49 @@ const definitions = {
|
||||
"k",
|
||||
"v"
|
||||
]
|
||||
},
|
||||
"U2fData": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"v": {
|
||||
"type": "string"
|
||||
},
|
||||
"d": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/U2fItem"
|
||||
}
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"d",
|
||||
"v"
|
||||
]
|
||||
},
|
||||
"U2fItem": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"u": {
|
||||
"type": "string"
|
||||
},
|
||||
"a": {
|
||||
"type": "number"
|
||||
},
|
||||
"h": {
|
||||
"type": "string"
|
||||
},
|
||||
"p": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"a",
|
||||
"h",
|
||||
"p",
|
||||
"u"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2907,6 +3014,9 @@ export const isSerializedParentAction: (value: unknown) => value is SerializedPa
|
||||
{
|
||||
"$ref": "#/definitions/SerializedAddCategoryNetworkIdAction"
|
||||
},
|
||||
{
|
||||
"$ref": "#/definitions/SerializedAddParentU2fKeyAction"
|
||||
},
|
||||
{
|
||||
"$ref": "#/definitions/SerializedAddUserAction"
|
||||
},
|
||||
@@ -2934,9 +3044,15 @@ export const isSerializedParentAction: (value: unknown) => value is SerializedPa
|
||||
{
|
||||
"$ref": "#/definitions/SerializedIncrementCategoryExtraTimeAction"
|
||||
},
|
||||
{
|
||||
"$ref": "#/definitions/SerializedReportU2fLoginAction"
|
||||
},
|
||||
{
|
||||
"$ref": "#/definitions/SerializedRemoveCategoryAppsAction"
|
||||
},
|
||||
{
|
||||
"$ref": "#/definitions/SerializedRemoveParentU2fKeyAction"
|
||||
},
|
||||
{
|
||||
"$ref": "#/definitions/SerializedRemoveUserAction"
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user