diff --git a/.gitea/workflows/build.yaml b/.gitea/workflows/build.yaml index 45aa628..732fb75 100644 --- a/.gitea/workflows/build.yaml +++ b/.gitea/workflows/build.yaml @@ -13,7 +13,7 @@ jobs: uses: actions/checkout@v3 - run: echo "The ${{ github.repository }} repository has been cloned to the runner." - run: echo "The workflow is now ready to test your code on the runner." - - name: List files in the repository: + - name: List files in the repository run: | ls ${{ github.workspace }} - run: echo "This job's status is ${{ job.status }}." diff --git a/app/Controllers/Http/Admin/mailsettings_controller.ts b/app/Controllers/Http/Admin/mailsettings_controller.ts index 43e9dc7..628a9fc 100644 --- a/app/Controllers/Http/Admin/mailsettings_controller.ts +++ b/app/Controllers/Http/Admin/mailsettings_controller.ts @@ -76,23 +76,24 @@ export default class MailSettingsController { public async sendTestMail({ response, auth }: HttpContext) { const user = auth.user!; const userEmail = user.email; - + // let mailManager = await app.container.make('mail.manager'); - // let iwas = mailManager.use(); + // let iwas = mailManager.use(); // let test = mail.config.mailers.smtp(); if (!userEmail) { return response.badRequest({ message: 'User email is not set. Please update your profile.' }); } try { - await mail.send((message) => { - message - // .from(Config.get('mail.from.address')) - .from('tethys@geosphere.at') - .to(userEmail) - .subject('Test Email') - .html('
If you received this email, the email configuration seems to be correct.
'); - }); + await mail.send( + (message) => { + message + // .from(Config.get('mail.from.address')) + .from('tethys@geosphere.at') + .to(userEmail) + .subject('Test Email') + .html('If you received this email, the email configuration seems to be correct.
'); + }); return response.json({ success: true, message: 'Test email sent successfully' }); // return response.flash('Test email sent successfully!', 'message').redirect().back(); diff --git a/app/Controllers/Http/Editor/DatasetController.ts b/app/Controllers/Http/Editor/DatasetController.ts index 04c450d..f2a0417 100644 --- a/app/Controllers/Http/Editor/DatasetController.ts +++ b/app/Controllers/Http/Editor/DatasetController.ts @@ -188,10 +188,16 @@ export default class DatasetsController { } } - public async approve({ request, inertia, response }: HttpContext) { + public async approve({ request, inertia, response, auth }: HttpContext) { const id = request.param('id'); + + const user = auth.user; + if (!user) { + return response.flash('You must be logged in to edit a dataset.', 'error').redirect().toRoute('app.login.show'); + } + // $dataset = Dataset::with('user:id,login')->findOrFail($id); - const dataset = await Dataset.findOrFail(id); + const dataset = await Dataset.query().where('id', id).where('editor_id', user.id).firstOrFail(); const validStates = ['editor_accepted', 'rejected_reviewer']; if (!validStates.includes(dataset.server_state)) { @@ -217,7 +223,7 @@ export default class DatasetsController { }); } - public async approveUpdate({ request, response }: HttpContext) { + public async approveUpdate({ request, response, auth }: HttpContext) { const approveDatasetSchema = vine.object({ reviewer_id: vine.number(), }); @@ -230,7 +236,11 @@ export default class DatasetsController { throw error; } const id = request.param('id'); - const dataset = await Dataset.findOrFail(id); + const user = auth.user; + if (!user) { + return response.flash('You must be logged in to edit a dataset.', 'error').redirect().toRoute('app.login.show'); + } + const dataset = await Dataset.query().where('id', id).where('editor_id', user.id).firstOrFail(); const validStates = ['editor_accepted', 'rejected_reviewer']; if (!validStates.includes(dataset.server_state)) { @@ -261,10 +271,15 @@ export default class DatasetsController { } } - public async reject({ request, inertia, response }: HttpContext) { + public async reject({ request, inertia, response, auth }: HttpContext) { const id = request.param('id'); + const user = auth.user; + if (!user) { + return response.flash('You must be logged in to edit a dataset.', 'error').redirect().toRoute('app.login.show'); + } const dataset = await Dataset.query() .where('id', id) + .where('editor_id', user.id) // Ensure the user is the editor of the dataset // .preload('titles') // .preload('descriptions') .preload('user', (builder) => { @@ -291,10 +306,15 @@ export default class DatasetsController { public async rejectUpdate({ request, response, auth }: HttpContext) { const authUser = auth.user!; + + if (!authUser) { + return response.flash('You must be logged in to edit a dataset.', 'error').redirect().toRoute('app.login.show'); + } const id = request.param('id'); const dataset = await Dataset.query() .where('id', id) + .where('editor_id', authUser.id) // Ensure the user is the editor of the dataset .preload('user', (builder) => { builder.select('id', 'login', 'email'); }) @@ -377,9 +397,14 @@ export default class DatasetsController { public async publish({ request, inertia, response, auth }: HttpContext) { const id = request.param('id'); + const user = auth.user; + if (!user) { + return response.flash('You must be logged in to edit a dataset.', 'error').redirect().toRoute('app.login.show'); + } const dataset = await Dataset.query() .where('id', id) + .where('editor_id', user.id) // Ensure the user is the editor of the dataset .preload('titles') .preload('authors') // .preload('persons', (builder) => { @@ -408,7 +433,7 @@ export default class DatasetsController { }); } - public async publishUpdate({ request, response }: HttpContext) { + public async publishUpdate({ request, response, auth }: HttpContext) { const publishDatasetSchema = vine.object({ publisher_name: vine.string().trim(), }); @@ -420,7 +445,12 @@ export default class DatasetsController { throw error; } const id = request.param('id'); - const dataset = await Dataset.findOrFail(id); + const user = auth.user; + if (!user) { + return response.flash('You must be logged in to edit a dataset.', 'error').redirect().toRoute('app.login.show'); + } + + const dataset = await Dataset.query().where('id', id).where('editor_id', user.id).firstOrFail(); // let test = await Dataset.getMax('publish_id'); // const maxPublishId = await Database.from('documents').max('publish_id as max_publish_id').first(); @@ -446,10 +476,16 @@ export default class DatasetsController { } } - public async rejectToReviewer({ request, inertia, response }: HttpContext) { + public async rejectToReviewer({ request, inertia, response, auth }: HttpContext) { const id = request.param('id'); + const user = auth.user; + if (!user) { + return response.flash('You must be logged in to edit a dataset.', 'error').redirect().toRoute('app.login.show'); + } + const dataset = await Dataset.query() .where('id', id) + .where('editor_id', user.id) // Ensure the user is the editor of the dataset .preload('reviewer', (builder) => { builder.select('id', 'login', 'email'); }) @@ -475,9 +511,14 @@ export default class DatasetsController { public async rejectToReviewerUpdate({ request, response, auth }: HttpContext) { const authUser = auth.user!; + if (!authUser) { + return response.flash('You must be logged in to edit a dataset.', 'error').redirect().toRoute('app.login.show'); + } + const id = request.param('id'); const dataset = await Dataset.query() .where('id', id) + .where('editor_id', authUser.id) // Ensure the user is the editor of the dataset .preload('reviewer', (builder) => { builder.select('id', 'login', 'email'); }) @@ -558,10 +599,16 @@ export default class DatasetsController { .toRoute('editor.dataset.list'); } - public async doiCreate({ request, inertia }: HttpContext) { + public async doiCreate({ request, inertia, auth, response }: HttpContext) { const id = request.param('id'); + const user = auth.user; + if (!user) { + return response.flash('You must be logged in to edit a dataset.', 'error').redirect().toRoute('app.login.show'); + } + const dataset = await Dataset.query() .where('id', id) + .where('editor_id', user.id) // Ensure the user is the editor of the dataset .preload('titles') .preload('descriptions') // .preload('identifier') @@ -572,11 +619,18 @@ export default class DatasetsController { }); } - public async doiStore({ request, response }: HttpContext) { + public async doiStore({ request, response, auth }: HttpContext) { const dataId = request.param('publish_id'); + const user = auth.user; + if (!user) { + return response.flash('You must be logged in to edit a dataset.', 'error').redirect().toRoute('app.login.show'); + } // Load dataset with minimal required relationships - const dataset = await Dataset.query().where('publish_id', dataId).firstOrFail(); + const dataset = await Dataset.query() + .where('editor_id', user.id) // Ensure the user is the editor of the dataset + .where('publish_id', dataId) + .firstOrFail(); const prefix = process.env.DATACITE_PREFIX || ''; const base_domain = process.env.BASE_DOMAIN || ''; @@ -658,9 +712,17 @@ export default class DatasetsController { public async show({}: HttpContext) {} - public async edit({ request, inertia, response }: HttpContext) { + public async edit({ request, inertia, response, auth }: HttpContext) { const id = request.param('id'); - const datasetQuery = Dataset.query().where('id', id); + + // Check if user is authenticated + const user = auth.user; + if (!user) { + return response.flash('You must be logged in to edit a dataset.', 'error').redirect().toRoute('app.login.show'); + } + + // Prefilter by both id AND editor_id to ensure user has permission to edit + const datasetQuery = Dataset.query().where('id', id).where('editor_id', user.id); datasetQuery .preload('titles', (query) => query.orderBy('id', 'asc')) .preload('descriptions', (query) => query.orderBy('id', 'asc')) @@ -677,6 +739,7 @@ export default class DatasetsController { query.orderBy('sort_order', 'asc'); // Sort by sort_order column }); + // This will throw 404 if editor_id does not match logged in user const dataset = await datasetQuery.firstOrFail(); const validStates = ['editor_accepted', 'rejected_reviewer']; if (!validStates.includes(dataset.server_state)) { @@ -750,11 +813,16 @@ export default class DatasetsController { }); } - public async update({ request, response, session }: HttpContext) { + public async update({ request, response, session, auth }: HttpContext) { // Get the dataset id from the route parameter const datasetId = request.param('id'); + const user = auth.user; + if (!user) { + return response.flash('You must be logged in to edit a dataset.', 'error').redirect().toRoute('app.login.show'); + } + // Retrieve the dataset and load its existing files - const dataset = await Dataset.findOrFail(datasetId); + const dataset = await Dataset.query().where('id', datasetId).where('editor_id', user.id).firstOrFail(); await dataset.load('files'); let trx: TransactionClientContract | null = null; @@ -763,7 +831,7 @@ export default class DatasetsController { trx = await db.transaction(); // const user = (await User.find(auth.user?.id)) as User; // await this.createDatasetAndAssociations(user, request, trx); - const dataset = await Dataset.findOrFail(datasetId); + // const dataset = await Dataset.findOrFail(datasetId); // save the licenses const licenses: number[] = request.input('licenses', []); @@ -949,10 +1017,15 @@ export default class DatasetsController { } } - public async categorize({ inertia, request, response }: HttpContext) { + public async categorize({ inertia, request, response, auth }: HttpContext) { const id = request.param('id'); + // Check if user is authenticated + const user = auth.user; + if (!user) { + return response.flash('You must be logged in to edit a dataset.', 'error').redirect().toRoute('app.login.show'); + } // Preload dataset and its "collections" relation - const dataset = await Dataset.query().where('id', id).preload('collections').firstOrFail(); + const dataset = await Dataset.query().where('id', id).where('editor_id', user.id).preload('collections').firstOrFail(); const validStates = ['editor_accepted', 'rejected_reviewer']; if (!validStates.includes(dataset.server_state)) { // session.flash('errors', 'Invalid server state!'); @@ -980,10 +1053,15 @@ export default class DatasetsController { }); } - public async categorizeUpdate({ request, response, session }: HttpContext) { + public async categorizeUpdate({ request, response, session, auth }: HttpContext) { // Get the dataset id from the route parameter const id = request.param('id'); - const dataset = await Dataset.query().preload('files').where('id', id).firstOrFail(); + const user = auth.user; + if (!user) { + return response.flash('You must be logged in to edit a dataset.', 'error').redirect().toRoute('app.login.show'); + } + // Retrieve the dataset and load its existing files + const dataset = await Dataset.query().preload('files').where('id', id).where('editor_id', user.id).firstOrFail(); const validStates = ['editor_accepted', 'rejected_reviewer']; if (!validStates.includes(dataset.server_state)) { @@ -1188,7 +1266,7 @@ export default class DatasetsController { } // return cache.getDomDocument(); - const xmlDocument : XMLBuilder | null = await serializer.toXmlDocument(); + const xmlDocument: XMLBuilder | null = await serializer.toXmlDocument(); return xmlDocument; } } diff --git a/app/Controllers/Http/Submitter/DatasetController.ts b/app/Controllers/Http/Submitter/DatasetController.ts index cda7da6..d666e6f 100644 --- a/app/Controllers/Http/Submitter/DatasetController.ts +++ b/app/Controllers/Http/Submitter/DatasetController.ts @@ -824,13 +824,20 @@ export default class DatasetController { }; // public async release({ params, view }) { - public async release({ request, inertia, response }: HttpContext) { + public async release({ request, inertia, response, auth }: HttpContext) { const id = request.param('id'); + const user = auth.user; + + // Check if user is authenticated + if (!user) { + return response.flash('You must be logged in to edit a dataset.', 'error').redirect().toRoute('app.login.show'); + } const dataset = await Dataset.query() .preload('user', (builder) => { builder.select('id', 'login'); }) + .where('account_id', user.id) // Only fetch if user owns it .where('id', id) .firstOrFail(); @@ -851,9 +858,20 @@ export default class DatasetController { }); } - public async releaseUpdate({ request, response }: HttpContext) { + public async releaseUpdate({ request, response, auth }: HttpContext) { const id = request.param('id'); - const dataset = await Dataset.query().preload('files').where('id', id).firstOrFail(); + const user = auth.user; + + // Check if user is authenticated + if (!user) { + return response.flash('You must be logged in to edit a dataset.', 'error').redirect().toRoute('app.login.show'); + } + + const dataset = await Dataset.query() + .preload('files') + .where('id', id) + .where('account_id', user.id) // Only fetch if user owns it + .firstOrFail(); const validStates = ['inprogress', 'rejected_editor']; if (!validStates.includes(dataset.server_state)) { @@ -933,7 +951,15 @@ export default class DatasetController { public async edit({ request, inertia, response, auth }: HttpContext) { const id = request.param('id'); - const datasetQuery = Dataset.query().where('id', id); + const user = auth.user; + + // Check if user is authenticated + if (!user) { + return response.flash('You must be logged in to edit a dataset.', 'error').redirect().toRoute('app.login.show'); + } + + // Prefilter by both id AND account_id + const datasetQuery = Dataset.query().where('id', id).where('account_id', user.id); // Only fetch if user owns it datasetQuery .preload('titles', (query) => query.orderBy('id', 'asc')) .preload('descriptions', (query) => query.orderBy('id', 'asc')) @@ -949,8 +975,9 @@ export default class DatasetController { .preload('files', (query) => { query.orderBy('sort_order', 'asc'); // Sort by sort_order column }); - + // This will throw 404 if dataset doesn't exist OR user doesn't own it const dataset = await datasetQuery.firstOrFail(); + const validStates = ['inprogress', 'rejected_editor']; if (!validStates.includes(dataset.server_state)) { // session.flash('errors', 'Invalid server state!'); @@ -1014,11 +1041,30 @@ export default class DatasetController { }); } - public async update({ request, response, session }: HttpContext) { + public async update({ request, response, session, auth }: HttpContext) { // Get the dataset id from the route parameter const datasetId = request.param('id'); - // Retrieve the dataset and load its existing files - const dataset = await Dataset.findOrFail(datasetId); + const user = auth.user; + + // Check if user is authenticated + if (!user) { + return response.flash('You must be logged in to update a dataset.', 'error').redirect().toRoute('app.login.show'); + } + + // Prefilter by both id AND account_id + const dataset = await Dataset.query() + .where('id', datasetId) + .where('account_id', user.id) // Only fetch if user owns it + .firstOrFail(); + + // // Check if the authenticated user is the owner of the dataset + // if (dataset.account_id !== user.id) { + // return response + // .flash(`Unauthorized access. You are not the owner of dataset with id ${id}.`, 'error') + // .redirect() + // .toRoute('dataset.list'); + // } + await dataset.load('files'); // Accumulate the size of the already related files // const preExistingFileSize = dataset.files.reduce((acc, file) => acc + file.fileSize, 0); @@ -1442,16 +1488,26 @@ export default class DatasetController { } } - public async delete({ request, inertia, response, session }: HttpContext) { + public async delete({ request, inertia, response, session, auth }: HttpContext) { const id = request.param('id'); + const user = auth.user; + + // Check if user is authenticated + if (!user) { + return response.flash('You must be logged in to edit a dataset.', 'error').redirect().toRoute('app.login.show'); + } + try { + // This will throw 404 if dataset doesn't exist OR user doesn't own it const dataset = await Dataset.query() .preload('user', (builder) => { builder.select('id', 'login'); }) .where('id', id) + .where('account_id', user.id) // Only fetch if user owns it .preload('files') .firstOrFail(); + const validStates = ['inprogress', 'rejected_editor']; if (!validStates.includes(dataset.server_state)) { // session.flash('errors', 'Invalid server state!'); @@ -1476,9 +1532,27 @@ export default class DatasetController { } } - public async deleteUpdate({ params, session, response }: HttpContext) { + public async deleteUpdate({ params, session, response, auth }: HttpContext) { try { - const dataset = await Dataset.query().where('id', params.id).preload('files').firstOrFail(); + const user = auth.user; + if (!user) { + return response.flash('You must be logged in to edit a dataset.', 'error').redirect().toRoute('app.login.show'); + } + + // This will throw 404 if dataset doesn't exist OR user doesn't own it + const dataset = await Dataset.query() + .where('id', params.id) + .where('account_id', user.id) // Only fetch if user owns it + .preload('files') + .firstOrFail(); + + // // Check if the authenticated user is the owner of the dataset + // if (dataset.account_id !== user.id) { + // return response + // .flash(`Unauthorized access. You are not the owner of dataset with id ${params.id}.`, 'error') + // .redirect() + // .toRoute('dataset.list'); + // } const validStates = ['inprogress', 'rejected_editor']; if (validStates.includes(dataset.server_state)) { diff --git a/config/mail.ts b/config/mail.ts index 8016c29..a97489f 100644 --- a/config/mail.ts +++ b/config/mail.ts @@ -16,7 +16,7 @@ const mailConfig = defineConfig({ host: env.get('SMTP_HOST', ''), port: env.get('SMTP_PORT'), secure: false, - // ignoreTLS: true, + ignoreTLS: true, requireTLS: false, /** diff --git a/resources/js/Components/Map/map.component.vue b/resources/js/Components/Map/map.component.vue index 353162b..0edd1db 100644 --- a/resources/js/Components/Map/map.component.vue +++ b/resources/js/Components/Map/map.component.vue @@ -87,8 +87,10 @@ import BaseIcon from '@/Components/BaseIcon.vue'; import { MapOptions } from './MapOptions'; import { LayerOptions, LayerMap } from './LayerOptions'; import { MapService } from '@/Stores/map.service'; -import { ZoomControlComponent } from './zoom.component.vue'; -import { DrawControlComponent } from './draw.component.vue'; +// import ZoomControlComponent from '@/Components/Map/zoom.component.vue'; +// import DrawControlComponent from '@/Components/Map/draw.component.vue'; +import ZoomControlComponent from './zoom.component.vue'; +import DrawControlComponent from './draw.component.vue'; import { Coverage } from '@/Dataset'; import { canvas } from 'leaflet/src/layer/vector/Canvas'; import { svg } from 'leaflet/src/layer/vector/SVG'; @@ -137,7 +139,7 @@ const DEFAULT_BASE_LAYER_ATTRIBUTION = '©