feat(bookings): restrict management to assigned PIC or super admin
Secure API endpoints with requireBookingManager authorization check Update confirmation page to prompt for login if unauthorized Add safe redirect handling to login and guest middleware
This commit is contained in:
@@ -109,6 +109,8 @@ const messages = {
|
|||||||
'confirm.alreadyPendingDescription': 'This booking was already pending confirmation.',
|
'confirm.alreadyPendingDescription': 'This booking was already pending confirmation.',
|
||||||
'confirm.cancelledDescription': 'The booking has been returned to pending status.',
|
'confirm.cancelledDescription': 'The booking has been returned to pending status.',
|
||||||
'confirm.cancelFailed': 'Cancellation failed',
|
'confirm.cancelFailed': 'Cancellation failed',
|
||||||
|
'confirm.signInToManage': 'Sign in to manage this booking',
|
||||||
|
'confirm.managementRestricted': 'Only the assigned PIC or Super Admin can manage this booking.',
|
||||||
'receipt.badge': 'Ticket Receipt',
|
'receipt.badge': 'Ticket Receipt',
|
||||||
'receipt.mainQr': 'Main QR',
|
'receipt.mainQr': 'Main QR',
|
||||||
'receipt.seatList': 'Seat List',
|
'receipt.seatList': 'Seat List',
|
||||||
@@ -277,6 +279,8 @@ const messages = {
|
|||||||
'confirm.alreadyPendingDescription': '此预订已经处于待确认状态。',
|
'confirm.alreadyPendingDescription': '此预订已经处于待确认状态。',
|
||||||
'confirm.cancelledDescription': '预订已回到待确认状态。',
|
'confirm.cancelledDescription': '预订已回到待确认状态。',
|
||||||
'confirm.cancelFailed': '取消失败',
|
'confirm.cancelFailed': '取消失败',
|
||||||
|
'confirm.signInToManage': '登录以管理此预订',
|
||||||
|
'confirm.managementRestricted': '只有指定负责人或 Super Admin 可以管理此预订。',
|
||||||
'receipt.badge': '票券收据',
|
'receipt.badge': '票券收据',
|
||||||
'receipt.mainQr': '主二维码',
|
'receipt.mainQr': '主二维码',
|
||||||
'receipt.seatList': '座位列表',
|
'receipt.seatList': '座位列表',
|
||||||
|
|||||||
@@ -1,6 +1,14 @@
|
|||||||
import { getDefaultAuthenticatedPath } from '~~/shared/auth'
|
import { getDefaultAuthenticatedPath } from '~~/shared/auth'
|
||||||
|
|
||||||
export default defineNuxtRouteMiddleware(async () => {
|
function getSafeRedirectPath(value: unknown) {
|
||||||
|
const redirect = Array.isArray(value) ? value[0] : value
|
||||||
|
|
||||||
|
return typeof redirect === 'string' && redirect.startsWith('/') && !redirect.startsWith('//')
|
||||||
|
? redirect
|
||||||
|
: null
|
||||||
|
}
|
||||||
|
|
||||||
|
export default defineNuxtRouteMiddleware(async (to) => {
|
||||||
const auth = useAuth()
|
const auth = useAuth()
|
||||||
await auth.fetchSession()
|
await auth.fetchSession()
|
||||||
|
|
||||||
@@ -8,5 +16,5 @@ export default defineNuxtRouteMiddleware(async () => {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
return navigateTo(getDefaultAuthenticatedPath(auth.user.value))
|
return navigateTo(getSafeRedirectPath(to.query.redirect) || getDefaultAuthenticatedPath(auth.user.value))
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import { formatDateTime } from '../../utils/formatters'
|
|||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const toast = useToast()
|
const toast = useToast()
|
||||||
const apiClient = useApiClient()
|
const apiClient = useApiClient()
|
||||||
|
const auth = useAuth()
|
||||||
const { locale, t } = useLocale()
|
const { locale, t } = useLocale()
|
||||||
|
|
||||||
const token = String(route.params.token || '')
|
const token = String(route.params.token || '')
|
||||||
@@ -27,6 +28,8 @@ const savingPayment = ref(false)
|
|||||||
const uploadingTransactionDocument = ref(false)
|
const uploadingTransactionDocument = ref(false)
|
||||||
const deletingTransactionDocument = ref(false)
|
const deletingTransactionDocument = ref(false)
|
||||||
|
|
||||||
|
await auth.fetchSession()
|
||||||
|
|
||||||
let initialBooking: PublicBooking
|
let initialBooking: PublicBooking
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -50,6 +53,12 @@ const statusColor = computed(() => booking.value.status === 'confirmed' ? 'succe
|
|||||||
const ticketLabel = computed(() => booking.value.ticketLabel || booking.value.ticketType.toUpperCase())
|
const ticketLabel = computed(() => booking.value.ticketLabel || booking.value.ticketType.toUpperCase())
|
||||||
const totalFormatted = computed(() => formatBookingCurrency(booking.value.totalPrice, locale.value))
|
const totalFormatted = computed(() => formatBookingCurrency(booking.value.totalPrice, locale.value))
|
||||||
const receiptPath = computed(() => `/receipt/${booking.value.receiptToken}`)
|
const receiptPath = computed(() => `/receipt/${booking.value.receiptToken}`)
|
||||||
|
const signInPath = computed(() => `/login?redirect=${encodeURIComponent(route.fullPath)}`)
|
||||||
|
const canManageBooking = computed(() => {
|
||||||
|
const user = auth.user.value
|
||||||
|
|
||||||
|
return Boolean(user && (user.role === 'super_admin' || user.id === booking.value.personInChargeId))
|
||||||
|
})
|
||||||
const transactionDocumentLimit = 10 * 1024 * 1024
|
const transactionDocumentLimit = 10 * 1024 * 1024
|
||||||
const transactionDocumentAccept = '.pdf,.jpg,.jpeg,.png,.webp,.heic,.heif,application/pdf,image/jpeg,image/png,image/webp,image/heic,image/heif'
|
const transactionDocumentAccept = '.pdf,.jpg,.jpeg,.png,.webp,.heic,.heif,application/pdf,image/jpeg,image/png,image/webp,image/heic,image/heif'
|
||||||
const selectedTransactionDocumentName = computed(() => transactionDocumentFile.value?.name || '')
|
const selectedTransactionDocumentName = computed(() => transactionDocumentFile.value?.name || '')
|
||||||
@@ -149,6 +158,10 @@ function formatFileSize(size: number) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function onTransactionDocumentChange(event: Event) {
|
function onTransactionDocumentChange(event: Event) {
|
||||||
|
if (!canManageBooking.value) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
const input = event.target as HTMLInputElement
|
const input = event.target as HTMLInputElement
|
||||||
transactionDocumentFile.value = input.files?.[0] ?? null
|
transactionDocumentFile.value = input.files?.[0] ?? null
|
||||||
}
|
}
|
||||||
@@ -159,7 +172,7 @@ function applyBooking(nextBooking: PublicBooking) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function savePaymentDetails() {
|
async function savePaymentDetails() {
|
||||||
if (savingPayment.value || booking.value.status !== 'pending') {
|
if (!canManageBooking.value || savingPayment.value || booking.value.status !== 'pending') {
|
||||||
return booking.value
|
return booking.value
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -185,7 +198,7 @@ async function savePaymentDetails() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function uploadTransactionDocument(options: { silent?: boolean } = {}) {
|
async function uploadTransactionDocument(options: { silent?: boolean } = {}) {
|
||||||
if (!transactionDocumentFile.value || uploadingTransactionDocument.value || booking.value.status !== 'pending') {
|
if (!canManageBooking.value || !transactionDocumentFile.value || uploadingTransactionDocument.value || booking.value.status !== 'pending') {
|
||||||
return booking.value
|
return booking.value
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -249,7 +262,7 @@ async function uploadTransactionDocument(options: { silent?: boolean } = {}) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function deleteTransactionDocument() {
|
async function deleteTransactionDocument() {
|
||||||
if (!booking.value.transactionDocument || deletingTransactionDocument.value || booking.value.status !== 'pending') {
|
if (!canManageBooking.value || !booking.value.transactionDocument || deletingTransactionDocument.value || booking.value.status !== 'pending') {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -290,7 +303,7 @@ async function deleteTransactionDocument() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function confirmBooking() {
|
async function confirmBooking() {
|
||||||
if (booking.value.status === 'confirmed') {
|
if (!canManageBooking.value || booking.value.status === 'confirmed') {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -334,7 +347,7 @@ async function confirmBooking() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function cancelBookingConfirmation() {
|
async function cancelBookingConfirmation() {
|
||||||
if (booking.value.status !== 'confirmed') {
|
if (!canManageBooking.value || booking.value.status !== 'confirmed') {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -413,6 +426,13 @@ async function cancelBookingConfirmation() {
|
|||||||
icon="i-lucide-badge-check"
|
icon="i-lucide-badge-check"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<UAlert
|
||||||
|
v-if="!canManageBooking"
|
||||||
|
:title="auth.user.value ? t('confirm.managementRestricted') : t('confirm.signInToManage')"
|
||||||
|
color="warning"
|
||||||
|
icon="i-lucide-lock-keyhole"
|
||||||
|
/>
|
||||||
|
|
||||||
<div class="overflow-hidden rounded-lg border border-default">
|
<div class="overflow-hidden rounded-lg border border-default">
|
||||||
<div
|
<div
|
||||||
v-for="row in detailRows"
|
v-for="row in detailRows"
|
||||||
@@ -437,7 +457,7 @@ async function cancelBookingConfirmation() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="space-y-3 rounded-lg border border-default p-4">
|
<div v-if="canManageBooking" class="space-y-3 rounded-lg border border-default p-4">
|
||||||
<div class="flex flex-col gap-1 sm:flex-row sm:items-center sm:justify-between">
|
<div class="flex flex-col gap-1 sm:flex-row sm:items-center sm:justify-between">
|
||||||
<div>
|
<div>
|
||||||
<h2 class="text-sm font-semibold text-highlighted">
|
<h2 class="text-sm font-semibold text-highlighted">
|
||||||
@@ -540,7 +560,7 @@ async function cancelBookingConfirmation() {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<UButton
|
<UButton
|
||||||
v-if="booking.status === 'confirmed'"
|
v-if="canManageBooking && booking.status === 'confirmed'"
|
||||||
:to="receiptPath"
|
:to="receiptPath"
|
||||||
:label="t('confirm.openReceipt')"
|
:label="t('confirm.openReceipt')"
|
||||||
color="neutral"
|
color="neutral"
|
||||||
@@ -550,7 +570,7 @@ async function cancelBookingConfirmation() {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<UButton
|
<UButton
|
||||||
v-if="booking.status === 'pending'"
|
v-if="canManageBooking && booking.status === 'pending'"
|
||||||
:label="t('confirm.confirmBooking')"
|
:label="t('confirm.confirmBooking')"
|
||||||
icon="i-lucide-check-check"
|
icon="i-lucide-check-check"
|
||||||
class="justify-center"
|
class="justify-center"
|
||||||
@@ -560,7 +580,7 @@ async function cancelBookingConfirmation() {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<UButton
|
<UButton
|
||||||
v-else
|
v-else-if="canManageBooking"
|
||||||
:label="t('confirm.cancelConfirmation')"
|
:label="t('confirm.cancelConfirmation')"
|
||||||
color="error"
|
color="error"
|
||||||
variant="outline"
|
variant="outline"
|
||||||
@@ -570,6 +590,16 @@ async function cancelBookingConfirmation() {
|
|||||||
:disabled="savingPayment || uploadingTransactionDocument || deletingTransactionDocument"
|
:disabled="savingPayment || uploadingTransactionDocument || deletingTransactionDocument"
|
||||||
@click="cancelBookingConfirmation"
|
@click="cancelBookingConfirmation"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<UButton
|
||||||
|
v-else-if="!auth.user.value"
|
||||||
|
:to="signInPath"
|
||||||
|
:label="t('confirm.signInToManage')"
|
||||||
|
color="primary"
|
||||||
|
variant="outline"
|
||||||
|
icon="i-lucide-log-in"
|
||||||
|
class="justify-center"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</UCard>
|
</UCard>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -128,6 +128,7 @@ useSeoMeta({
|
|||||||
})
|
})
|
||||||
|
|
||||||
const toast = useToast()
|
const toast = useToast()
|
||||||
|
const route = useRoute()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const auth = useAuth()
|
const auth = useAuth()
|
||||||
const apiClient = useApiClient()
|
const apiClient = useApiClient()
|
||||||
@@ -141,6 +142,14 @@ const form = reactive({
|
|||||||
const passwordPending = ref(false)
|
const passwordPending = ref(false)
|
||||||
const passkeyPending = ref(false)
|
const passkeyPending = ref(false)
|
||||||
|
|
||||||
|
function getSafeRedirectPath(value: unknown) {
|
||||||
|
const redirect = Array.isArray(value) ? value[0] : value
|
||||||
|
|
||||||
|
return typeof redirect === 'string' && redirect.startsWith('/') && !redirect.startsWith('//')
|
||||||
|
? redirect
|
||||||
|
: null
|
||||||
|
}
|
||||||
|
|
||||||
function validateLogin(state: typeof form): FormError[] {
|
function validateLogin(state: typeof form): FormError[] {
|
||||||
const errors: FormError[] = []
|
const errors: FormError[] = []
|
||||||
|
|
||||||
@@ -160,7 +169,7 @@ async function finishLogin(user: Awaited<ReturnType<typeof auth.fetchSession>>)
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
await router.push(getDefaultAuthenticatedPath(user))
|
await router.push(getSafeRedirectPath(route.query.redirect) || getDefaultAuthenticatedPath(user))
|
||||||
}
|
}
|
||||||
|
|
||||||
async function onSubmit(event: FormSubmitEvent<typeof form>) {
|
async function onSubmit(event: FormSubmitEvent<typeof form>) {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import type { CancelBookingConfirmationResponse } from '~~/shared/booking'
|
import type { CancelBookingConfirmationResponse } from '~~/shared/booking'
|
||||||
|
|
||||||
|
import { requireBookingManager } from '../../../../utils/auth'
|
||||||
import { cancelBookingConfirmationByConfirmationToken, getBookingByConfirmationToken } from '../../../../utils/booking-repository'
|
import { cancelBookingConfirmationByConfirmationToken, getBookingByConfirmationToken } from '../../../../utils/booking-repository'
|
||||||
import { getRequiredRouteParam, httpError } from '../../../../utils/http'
|
import { getRequiredRouteParam, httpError } from '../../../../utils/http'
|
||||||
|
|
||||||
@@ -11,6 +12,8 @@ export default defineEventHandler(async (event): Promise<CancelBookingConfirmati
|
|||||||
httpError(404, 'Booking not found')
|
httpError(404, 'Booking not found')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await requireBookingManager(event, existingBooking)
|
||||||
|
|
||||||
if (existingBooking.status === 'pending') {
|
if (existingBooking.status === 'pending') {
|
||||||
return {
|
return {
|
||||||
booking: existingBooking,
|
booking: existingBooking,
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import type { ConfirmBookingResponse } from '~~/shared/booking'
|
import type { ConfirmBookingResponse } from '~~/shared/booking'
|
||||||
|
|
||||||
|
import { requireBookingManager } from '../../../../utils/auth'
|
||||||
import { confirmBookingByConfirmationToken, getBookingByConfirmationToken, getBookingInventorySummary } from '../../../../utils/booking-repository'
|
import { confirmBookingByConfirmationToken, getBookingByConfirmationToken, getBookingInventorySummary } from '../../../../utils/booking-repository'
|
||||||
import { getRequiredRouteParam, httpError } from '../../../../utils/http'
|
import { getRequiredRouteParam, httpError } from '../../../../utils/http'
|
||||||
import { sendBookingTicketReceiptViaWhatsApp } from '../../../../utils/whatsapp'
|
import { sendBookingTicketReceiptViaWhatsApp } from '../../../../utils/whatsapp'
|
||||||
@@ -12,6 +13,8 @@ export default defineEventHandler(async (event): Promise<ConfirmBookingResponse>
|
|||||||
httpError(404, 'Booking not found')
|
httpError(404, 'Booking not found')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await requireBookingManager(event, existingBooking)
|
||||||
|
|
||||||
if (existingBooking.status === 'confirmed') {
|
if (existingBooking.status === 'confirmed') {
|
||||||
return {
|
return {
|
||||||
booking: existingBooking,
|
booking: existingBooking,
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import type { UpdateBookingDetailsResponse } from '~~/shared/booking'
|
import type { UpdateBookingDetailsResponse } from '~~/shared/booking'
|
||||||
|
|
||||||
|
import { requireBookingManager } from '../../../../utils/auth'
|
||||||
import {
|
import {
|
||||||
clearBookingTransactionDocumentByConfirmationToken,
|
clearBookingTransactionDocumentByConfirmationToken,
|
||||||
getBookingByConfirmationToken,
|
getBookingByConfirmationToken,
|
||||||
@@ -17,6 +18,8 @@ export default defineEventHandler(async (event): Promise<UpdateBookingDetailsRes
|
|||||||
httpError(404, 'Booking not found')
|
httpError(404, 'Booking not found')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await requireBookingManager(event, existingBooking)
|
||||||
|
|
||||||
if (existingBooking.status !== 'pending') {
|
if (existingBooking.status !== 'pending') {
|
||||||
httpError(409, 'Payment details can only be changed before confirmation')
|
httpError(409, 'Payment details can only be changed before confirmation')
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import type { UpdateBookingDetailsResponse } from '~~/shared/booking'
|
import type { UpdateBookingDetailsResponse } from '~~/shared/booking'
|
||||||
|
|
||||||
|
import { requireBookingManager } from '../../../../utils/auth'
|
||||||
import {
|
import {
|
||||||
clearBookingTransactionDocumentByConfirmationToken,
|
clearBookingTransactionDocumentByConfirmationToken,
|
||||||
getBookingByConfirmationToken
|
getBookingByConfirmationToken
|
||||||
@@ -15,6 +16,8 @@ export default defineEventHandler(async (event): Promise<UpdateBookingDetailsRes
|
|||||||
httpError(404, 'Booking not found')
|
httpError(404, 'Booking not found')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await requireBookingManager(event, booking)
|
||||||
|
|
||||||
if (booking.status !== 'pending') {
|
if (booking.status !== 'pending') {
|
||||||
httpError(409, 'Transaction document can only be changed before confirmation')
|
httpError(409, 'Transaction document can only be changed before confirmation')
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { sendStream, setHeader } from 'h3'
|
import { sendStream, setHeader } from 'h3'
|
||||||
|
|
||||||
import { getBookingTransactionDocumentByConfirmationToken } from '../../../../utils/booking-repository'
|
import { requireBookingManager } from '../../../../utils/auth'
|
||||||
|
import { getBookingByConfirmationToken, getBookingTransactionDocumentByConfirmationToken } from '../../../../utils/booking-repository'
|
||||||
import { getRequiredRouteParam, httpError } from '../../../../utils/http'
|
import { getRequiredRouteParam, httpError } from '../../../../utils/http'
|
||||||
import {
|
import {
|
||||||
getSafeDownloadName,
|
getSafeDownloadName,
|
||||||
@@ -9,6 +10,14 @@ import {
|
|||||||
|
|
||||||
export default defineEventHandler(async (event) => {
|
export default defineEventHandler(async (event) => {
|
||||||
const token = getRequiredRouteParam(event, 'token', 'Confirmation token')
|
const token = getRequiredRouteParam(event, 'token', 'Confirmation token')
|
||||||
|
const booking = await getBookingByConfirmationToken(token)
|
||||||
|
|
||||||
|
if (!booking) {
|
||||||
|
httpError(404, 'Booking not found')
|
||||||
|
}
|
||||||
|
|
||||||
|
await requireBookingManager(event, booking)
|
||||||
|
|
||||||
const document = await getBookingTransactionDocumentByConfirmationToken(token)
|
const document = await getBookingTransactionDocumentByConfirmationToken(token)
|
||||||
|
|
||||||
if (!document) {
|
if (!document) {
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import type { UpdateBookingDetailsResponse } from '~~/shared/booking'
|
|||||||
|
|
||||||
import { getHeader, readMultipartFormData } from 'h3'
|
import { getHeader, readMultipartFormData } from 'h3'
|
||||||
|
|
||||||
|
import { requireBookingManager } from '../../../../utils/auth'
|
||||||
import {
|
import {
|
||||||
getBookingByConfirmationToken,
|
getBookingByConfirmationToken,
|
||||||
replaceBookingTransactionDocumentByConfirmationToken
|
replaceBookingTransactionDocumentByConfirmationToken
|
||||||
@@ -22,6 +23,8 @@ export default defineEventHandler(async (event): Promise<UpdateBookingDetailsRes
|
|||||||
httpError(404, 'Booking not found')
|
httpError(404, 'Booking not found')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await requireBookingManager(event, booking)
|
||||||
|
|
||||||
if (booking.status !== 'pending') {
|
if (booking.status !== 'pending') {
|
||||||
httpError(409, 'Transaction document can only be changed before confirmation')
|
httpError(409, 'Transaction document can only be changed before confirmation')
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import type { H3Event } from 'h3'
|
import type { H3Event } from 'h3'
|
||||||
|
import type { PublicBooking } from '~~/shared/booking'
|
||||||
|
|
||||||
import { normalizeUsername, type UserRole } from '~~/shared/auth'
|
import { normalizeUsername, type UserRole } from '~~/shared/auth'
|
||||||
|
|
||||||
@@ -54,6 +55,19 @@ export async function requireRole(event: H3Event, role: UserRole) {
|
|||||||
return auth
|
return auth
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function requireBookingManager(event: H3Event, booking: Pick<PublicBooking, 'personInChargeId'>) {
|
||||||
|
const auth = await requireAuth(event)
|
||||||
|
|
||||||
|
if (auth.user.role !== 'super_admin' && auth.user.id !== booking.personInChargeId) {
|
||||||
|
throw createError({
|
||||||
|
statusCode: 403,
|
||||||
|
statusMessage: 'You are not allowed to manage this booking'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return auth
|
||||||
|
}
|
||||||
|
|
||||||
export async function signInUser(event: H3Event, user: UserAuthRecord, remember: boolean) {
|
export async function signInUser(event: H3Event, user: UserAuthRecord, remember: boolean) {
|
||||||
await createUserSession(event, {
|
await createUserSession(event, {
|
||||||
userId: user.id,
|
userId: user.id,
|
||||||
|
|||||||
Reference in New Issue
Block a user