Skip to content

Commit 6825557

Browse files
Merge pull request #336 from voidauth/fix/passkey-mgmt-sort
Fix Passkey Management Sort
2 parents 19a2c5d + fc37802 commit 6825557

4 files changed

Lines changed: 63 additions & 56 deletions

File tree

frontend/src/app/pages/home/home.component.html

Lines changed: 36 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -83,47 +83,45 @@ <h2 style="margin-top: -16px">Passkeys</h2>
8383
<mat-icon fontSet="material-icons-round" matSuffix>key</mat-icon>
8484
</button>
8585

86-
@if (passkeys.data.length) {
87-
<mat-expansion-panel class="key-manage-panel">
88-
<mat-expansion-panel-header>
89-
<mat-panel-title>Manage Passkeys</mat-panel-title>
90-
</mat-expansion-panel-header>
91-
<table mat-table [dataSource]="passkeys" matSort>
92-
@for (column of passkeyColumns; track column) {
93-
<ng-container [matColumnDef]="column.columnDef">
94-
<th mat-header-cell *matHeaderCellDef mat-sort-header>
95-
{{ column.header }}
96-
</th>
97-
<td mat-cell *matCellDef="let row">
98-
@if (column.isIcon) {
99-
<mat-icon fontSet="material-icons-round">{{ column.cell(row) }}</mat-icon>
100-
} @else {
101-
{{ column.cell(row) }}
102-
}
103-
</td>
104-
</ng-container>
105-
}
106-
107-
<ng-container matColumnDef="actions">
108-
<th mat-header-cell *matHeaderCellDef></th>
86+
<mat-expansion-panel class="key-manage-panel" [ngStyle]="{ display: passkeys.data.length ? 'block' : 'none' }">
87+
<mat-expansion-panel-header>
88+
<mat-panel-title>Manage Passkeys</mat-panel-title>
89+
</mat-expansion-panel-header>
90+
<table mat-table [dataSource]="passkeys" matSort>
91+
@for (column of passkeyColumns; track column) {
92+
<ng-container [matColumnDef]="column.columnDef">
93+
<th mat-header-cell *matHeaderCellDef mat-sort-header>
94+
{{ column.header }}
95+
</th>
10996
<td mat-cell *matCellDef="let row">
110-
<button
111-
mat-icon-button
112-
type="button"
113-
[disabled]="passkeys.data.length < 2 && !user?.hasPassword"
114-
(click)="deletePasskey(row.id)"
115-
matTooltip="Delete"
116-
>
117-
<mat-icon fontSet="material-icons-round">delete_forever</mat-icon>
118-
</button>
97+
@if (column.isIcon) {
98+
<mat-icon fontSet="material-icons-round">{{ column.cell(row) }}</mat-icon>
99+
} @else {
100+
{{ column.cell(row) }}
101+
}
119102
</td>
120103
</ng-container>
121-
122-
<tr mat-header-row *matHeaderRowDef="displayedPasskeyColumns"></tr>
123-
<tr mat-row *matRowDef="let row; columns: displayedPasskeyColumns"></tr>
124-
</table>
125-
</mat-expansion-panel>
126-
}
104+
}
105+
106+
<ng-container matColumnDef="actions">
107+
<th mat-header-cell *matHeaderCellDef></th>
108+
<td mat-cell *matCellDef="let row">
109+
<button
110+
mat-icon-button
111+
type="button"
112+
[disabled]="passkeys.data.length < 2 && !user?.hasPassword"
113+
(click)="deletePasskey(row.id)"
114+
matTooltip="Delete"
115+
>
116+
<mat-icon fontSet="material-icons-round">delete_forever</mat-icon>
117+
</button>
118+
</td>
119+
</ng-container>
120+
121+
<tr mat-header-row *matHeaderRowDef="displayedPasskeyColumns"></tr>
122+
<tr mat-row *matRowDef="let row; columns: displayedPasskeyColumns"></tr>
123+
</table>
124+
</mat-expansion-panel>
127125

128126
<mat-divider class="disabled"></mat-divider>
129127

frontend/src/app/pages/home/home.component.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import type { PasskeyResponse } from '@shared/api-response/PasskeyResponse'
2020
import { MatTableDataSource } from '@angular/material/table'
2121
import type { TableColumn } from '../admin/clients/clients.component'
2222
import { MatSort } from '@angular/material/sort'
23+
import { CommonModule } from '@angular/common'
2324

2425
@Component({
2526
selector: 'app-home',
@@ -28,6 +29,7 @@ import { MatSort } from '@angular/material/sort'
2829
MaterialModule,
2930
ValidationErrorPipe,
3031
PasswordSetComponent,
32+
CommonModule,
3133
],
3234
templateUrl: './home.component.html',
3335
styleUrl: './home.component.scss',

server/db/passkey.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { createExpiration } from './util'
66
import { TABLES, TTLs } from '@shared/constants'
77

88
export async function getUserPasskeys(userId: string) {
9-
return (await db().select().table<Passkey>(TABLES.PASSKEY).where({ userId })).map((p) => {
9+
return (await db().select().table<Passkey>(TABLES.PASSKEY).where({ userId }).orderBy('createdAt', 'desc')).map((p) => {
1010
return {
1111
...p,
1212
transports: p.transports?.split(',') as AuthenticatorTransportFuture[] | undefined,

server/util/zodValidate.ts

Lines changed: 24 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
1-
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
2-
/* eslint-disable @typescript-eslint/no-explicit-any */
31
import type { SchemaInfer } from '@shared/utils'
42
import type { RequestHandler } from 'express'
53
import type { ParsedQs } from 'qs'
64
import zod from 'zod'
5+
import { logger } from './logger'
76

87
// Enforce proper typing of params and query inputs
98
type ZodParamsShape = {
@@ -56,41 +55,49 @@ export function zodValidate<
5655
body?: BodyShape
5756
query?: QueryShape
5857
params?: ParamsShape
58+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
5959
}): RequestHandler<ShapeOrUndefined<ParamsShape>, any, ShapeOrUndefined<BodyShape>, ShapeOrUndefined<QueryShape>> {
6060
return (req, res, next) => {
61+
const errors: Record<string, unknown> = {}
6162
if (schema.params) {
6263
const output = zod.object(schema.params).safeParse(req.params)
63-
if (!output.success) {
64-
res.status(422).json(zod.treeifyError(output.error))
65-
return
64+
if (output.success) {
65+
req.params = output.data as ShapeOrUndefined<ParamsShape>
66+
} else {
67+
errors.params = zod.treeifyError(output.error)
6668
}
67-
req.params = output.data as any
6869
} else {
69-
req.params = undefined as any
70+
req.params = undefined as ShapeOrUndefined<ParamsShape>
7071
}
7172

7273
// Needed to make req.query writable
7374
Object.defineProperty(req, 'query', { ...Object.getOwnPropertyDescriptor(req, 'query'), value: req.query, writable: true })
7475
if (schema.query) {
7576
const output = zod.object(schema.query).safeParse(req.query)
76-
if (!output.success) {
77-
res.status(422).json(zod.treeifyError(output.error))
78-
return
77+
if (output.success) {
78+
req.query = output.data as ShapeOrUndefined<QueryShape>
79+
} else {
80+
errors.query = zod.treeifyError(output.error)
7981
}
80-
req.query = output.data as any
8182
} else {
82-
req.query = undefined as any
83+
req.query = undefined as ShapeOrUndefined<QueryShape>
8384
}
8485

8586
if (schema.body) {
8687
const output = zod.object(schema.body).safeParse(req.body)
87-
if (!output.success) {
88-
res.status(422).json(zod.treeifyError(output.error))
89-
return
88+
if (output.success) {
89+
req.body = output.data as ShapeOrUndefined<BodyShape>
90+
} else {
91+
errors.body = zod.treeifyError(output.error)
9092
}
91-
req.body = output.data as any
9293
} else {
93-
req.body = undefined as any
94+
req.body = undefined as ShapeOrUndefined<BodyShape>
95+
}
96+
97+
if (errors.params || errors.query || errors.body) {
98+
res.status(422).json(errors)
99+
logger.debug({ message: 'API Validation failed', path: req.path, method: req.method, error: errors })
100+
return
94101
}
95102

96103
next()

0 commit comments

Comments
 (0)