- added working typescript support to vue

- added bouncer support
- added UserPolicies
- added first user index call
This commit is contained in:
Sockenklaus
2023-07-08 08:16:08 +02:00
parent 6d28aaecb7
commit 864da02de5
23 changed files with 619 additions and 582 deletions

View File

@@ -5,7 +5,8 @@
"@adonisjs/core/build/commands/index.js", "@adonisjs/core/build/commands/index.js",
"@adonisjs/repl/build/commands", "@adonisjs/repl/build/commands",
"@eidellev/inertia-adonisjs/build/commands", "@eidellev/inertia-adonisjs/build/commands",
"@adonisjs/lucid/build/commands" "@adonisjs/lucid/build/commands",
"@adonisjs/bouncer/build/commands"
], ],
"exceptionHandlerNamespace": "App/Exceptions/Handler", "exceptionHandlerNamespace": "App/Exceptions/Handler",
"aliases": { "aliases": {
@@ -22,7 +23,8 @@
"environment": [ "environment": [
"web" "web"
] ]
} },
"./start/bouncer"
], ],
"providers": [ "providers": [
"./providers/AppProvider", "./providers/AppProvider",
@@ -32,7 +34,8 @@
"@adonisjs/shield", "@adonisjs/shield",
"@eidellev/inertia-adonisjs", "@eidellev/inertia-adonisjs",
"@adonisjs/lucid", "@adonisjs/lucid",
"@adonisjs/auth" "@adonisjs/auth",
"@adonisjs/bouncer"
], ],
"metaFiles": [ "metaFiles": [
{ {

3
.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,3 @@
{
"typescript.tsdk": "node_modules/typescript/lib"
}

View File

@@ -591,6 +591,42 @@
"description": "Disable locks acquired to run migrations safely" "description": "Disable locks acquired to run migrations safely"
} }
] ]
},
"make:policy": {
"settings": {},
"commandPath": "@adonisjs/bouncer/build/commands/MakePolicy",
"commandName": "make:policy",
"description": "Make a new bouncer policy",
"args": [
{
"type": "string",
"propertyName": "name",
"name": "name",
"required": true,
"description": "Name of the policy to create"
}
],
"aliases": [],
"flags": [
{
"name": "resource-model",
"propertyName": "resourceModel",
"type": "string",
"description": "Name of the resource model to authorize"
},
{
"name": "user-model",
"propertyName": "userModel",
"type": "string",
"description": "Name of the user model to be authorized"
},
{
"name": "actions",
"propertyName": "actions",
"type": "array",
"description": "Actions to implement"
}
]
} }
}, },
"aliases": {} "aliases": {}

View File

@@ -2,7 +2,7 @@ import type { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
import { schema } from '@ioc:Adonis/Core/Validator' import { schema } from '@ioc:Adonis/Core/Validator'
export default class AuthController { export default class AuthController {
public async login({ auth, request, response, session }: HttpContextContract){ public async login({ auth, request, response }: HttpContextContract){
const loginSchema = schema.create({ const loginSchema = schema.create({
@@ -18,27 +18,13 @@ export default class AuthController {
} }
}) })
session.flash({
login: {
warning: 'test'
}
})
await auth.attempt(username, password) await auth.attempt(username, password)
response.redirect().toRoute('events.index') response.redirect().toRoute('events.index')
} }
public async logout({ auth, response, session }: HttpContextContract) { public async logout({ auth, response }: HttpContextContract) {
await auth.logout() await auth.logout()
session.flash('gfd', {
warning: 'test'
})
session.flash('login', {
warning: "noch eine warning"
})
response.redirect('/login') response.redirect('/login')
} }
} }

View File

@@ -1,14 +1,17 @@
import type { HttpContextContract } from '@ioc:Adonis/Core/HttpContext' import type { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
import User from 'App/Models/User' import User from 'App/Models/User'
import Logger from '@ioc:Adonis/Core/Logger' import Database from '@ioc:Adonis/Lucid/Database'
export default class UsersController { export default class UsersController {
public async index({ auth, response, inertia }: HttpContextContract) { public async index({ inertia, bouncer }: HttpContextContract) {
if(auth.user?.isAdmin) { await bouncer.with('UserPolicy').authorize('index')
return inertia.render('Users/Index')
} const users = await Database
else response.redirect().toRoute('events.index') .from('users')
.select('id', 'username', 'is_admin')
return inertia.render('Users/Index', { users })
} }
public async create({ auth, inertia }: HttpContextContract) { public async create({ auth, inertia }: HttpContextContract) {

View File

@@ -38,6 +38,10 @@ export default class ExceptionHandler extends HttpExceptionHandler {
session.flash('login', { error: error.message }); session.flash('login', { error: error.message });
return response.redirect().back(); return response.redirect().back();
} }
if(['E_AUTHORIZATION_FAILURE'].includes(error.code)) {
session.flash('auth', { error: error.message })
return response.redirect().back()
}
/** /**
* Forward rest of the exceptions to the parent class * Forward rest of the exceptions to the parent class

View File

@@ -12,7 +12,10 @@ export default class User extends BaseModel {
@column({ serializeAs: null }) @column({ serializeAs: null })
public password: string public password: string
@column() @column({
consume: Boolean,
serialize: Boolean
})
public isAdmin: boolean public isAdmin: boolean
@column() @column()

View File

@@ -0,0 +1,25 @@
import User from 'App/Models/User'
import { BasePolicy } from '@ioc:Adonis/Addons/Bouncer'
export default class UserPolicy extends BasePolicy {
public async index(user: User) {
return user.isAdmin
}
public async show(user: User, query: User) {
return user.isAdmin || user.id === query.id
}
public async update(user: User, query: User) {
return user.isAdmin || user.id === query.id
}
public async destroy(user: User) {
return user.isAdmin
}
public async store(user: User) {
return user.isAdmin
}
}

16
contracts/bouncer.ts Normal file
View File

@@ -0,0 +1,16 @@
/**
* Contract source: https://git.io/Jte3v
*
* Feel free to let us know via PR, if you find something broken in this config
* file.
*/
import { actions, policies } from '../start/bouncer'
declare module '@ioc:Adonis/Addons/Bouncer' {
type ApplicationActions = ExtractActionsTypes<typeof actions>
type ApplicationPolicies = ExtractPoliciesTypes<typeof policies>
interface ActionsList extends ApplicationActions {}
interface PoliciesList extends ApplicationPolicies {}
}

883
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -67,6 +67,7 @@
}, },
"dependencies": { "dependencies": {
"@adonisjs/auth": "^8.2.3", "@adonisjs/auth": "^8.2.3",
"@adonisjs/bouncer": "^2.3.0",
"@adonisjs/core": "^5.9.0", "@adonisjs/core": "^5.9.0",
"@adonisjs/lucid": "^18.4.0", "@adonisjs/lucid": "^18.4.0",
"@adonisjs/repl": "^3.1.11", "@adonisjs/repl": "^3.1.11",
@@ -76,11 +77,13 @@
"@eidellev/inertia-adonisjs": "^8.0.1", "@eidellev/inertia-adonisjs": "^8.0.1",
"@inertiajs/vue3": "^1.0.9", "@inertiajs/vue3": "^1.0.9",
"@vue/compiler-sfc": "^3.3.4", "@vue/compiler-sfc": "^3.3.4",
"@vue/tsconfig": "^0.1.3",
"luxon": "^3.3.0", "luxon": "^3.3.0",
"proxy-addr": "^2.0.7", "proxy-addr": "^2.0.7",
"reflect-metadata": "^0.1.13", "reflect-metadata": "^0.1.13",
"source-map-support": "^0.5.21", "source-map-support": "^0.5.21",
"sqlite3": "^5.1.6", "sqlite3": "^5.1.6",
"ts-loader": "^9.4.4",
"vue": "^3.3.4" "vue": "^3.3.4"
} }
} }

View File

@@ -5,3 +5,6 @@
</div> </div>
</main> </main>
</template> </template>
<script setup lang="ts">
</script>

View File

@@ -1,8 +1,8 @@
<template> <template>
</template> </template>
<script setup> <script setup lang="ts">
import { useMessage } from 'naive-ui' import { useMessage } from 'naive-ui'
import { watch, defineProps, onUpdated, onMounted } from 'vue' import { onUpdated, onMounted } from 'vue'
const message = useMessage() const message = useMessage()
const props = defineProps(['messages']) const props = defineProps(['messages'])
@@ -15,16 +15,13 @@
displayNewMessages(props.messages) displayNewMessages(props.messages)
}) })
function displayNewMessages(messages) { function displayNewMessages(messages: any) {
console.log(messages) let output: Array<Object> = []
let output = []
output = flattenObject(removeValidationErrors(messages)) output = flattenObject(removeValidationErrors(messages))
console.log(output) output?.forEach((item: any) => {
output?.forEach((item) => {
for (let key in item) { for (let key in item) {
switch (key){ switch (key){
case 'error': case 'error':
@@ -55,33 +52,32 @@
}) })
} }
function removeValidationErrors(input) { function removeValidationErrors(input: any) {
if(input === null || !Object.hasOwn(input, "errors")) return input
if(input === null || !input.hasOwnProperty("errors")) return input
const { errors: _, ...output } = input const { errors: _, ...output } = (input as any)
return output return output
} }
function flattenObject(input) { function flattenObject(input: Object) {
if (input === null) return input if (input === null) return input
return Object.values(input).map((value) => Object.entries(value)).flat().reduce((acc, [key, value]) => { return Object.values(input).map((value) => Object.entries(value)).flat().reduce((acc: Array<Object>, [key, value]) => {
acc.push({[key]: value}); acc.push({[key]: value});
return acc; return acc;
}, []); }, []);
} }
function translateError(errorMsg) { function translateError(errorMsg: string) {
switch(errorMsg.split(":")[0]) { switch(errorMsg.split(":")[0]) {
case 'E_INVALID_AUTH_PASSWORD': case 'E_INVALID_AUTH_PASSWORD':
return "Falsches Passwort eingegeben." return "Falsches Passwort eingegeben."
case 'E_INVALID_AUTH_UID': case 'E_INVALID_AUTH_UID':
return "Benutzername nicht gefunden" return "Benutzername nicht gefunden"
case 'E_AUTHORIZATION_FAILURE':
return 'Rechte unzureichend um diese Aktion auszuführen.'
} }
return errorMsg return errorMsg
} }
function isEmpty(obj) {
return Object.keys(obj).length === 0;
}
</script> </script>

View File

@@ -1,15 +1,25 @@
<template> <template>
<n-message-provider>
<FlashMessages
:messages="props.flashMessages"
/>
</n-message-provider>
<div> <div>
Bin in Events Bin in Events
</div> </div>
</template> </template>
<script setup> <script setup lang="ts">
import BELayout from '@/layouts/BELayout.vue' import BELayout from '@/layouts/BELayout.vue'
import FlashMessages from '@/components/FlashMessages.vue'
defineOptions({ defineOptions({
layout: BELayout layout: BELayout
}) })
const props = defineProps({
flashMessages: Object
})
</script> </script>

View File

@@ -1,13 +1,26 @@
<template> <template>
<n-message-provider>
<FlashMessages
:messages="props.flashMessages"
/>
</n-message-provider>
<div> <div>
Willkommen auf der Hauptseite Willkommen auf der Hauptseite
</div> </div>
<div>
{{ helloWorld }}
</div>
</template> </template>
<script setup> <script setup lang="ts">
import { ref } from 'vue'
import FlashMessages from '@/components/FlashMessages.vue'
defineProps({ const props = defineProps({
test: String, test: String,
flashMessages: Object
}) })
const helloWorld = ref<String>("helloWorld")
</script> </script>

View File

@@ -46,11 +46,12 @@
</n-form> </n-form>
</template> </template>
<script setup> <script setup lang="ts">
import { ref, reactive, watch } from 'vue' import { ref } from 'vue'
import { router } from '@inertiajs/vue3' import { router } from '@inertiajs/vue3'
import type { FormInst } from 'naive-ui'
import LoginLayout from '@/layouts/LoginLayout.vue' import LoginLayout from '@/layouts/LoginLayout.vue'
import FlashMessages from '@/components/FlashMessages' import FlashMessages from '@/components/FlashMessages.vue'
defineOptions({ layout: LoginLayout }) defineOptions({ layout: LoginLayout })
const props = defineProps(['flashMessages']) const props = defineProps(['flashMessages'])
@@ -60,7 +61,7 @@
password: '', password: '',
}) })
const formRef = ref(null) const formRef = ref<FormInst | null>(null)
const rules = ref({ const rules = ref({
username: { username: {
@@ -75,15 +76,6 @@
} }
}) })
function translateLoginError(errorMsg) {
switch(errorMsg.split(":")[0]) {
case 'E_INVALID_AUTH_PASSWORD':
return "Falsches Passwort eingegeben."
case 'E_INVALID_AUTH_UID':
return "Benutzername nicht gefunden"
}
}
function onClickLogin(){ function onClickLogin(){
formRef.value?.validate((errors) => { formRef.value?.validate((errors) => {
if(!errors) router.post('login', form.value) if(!errors) router.post('login', form.value)

View File

@@ -1,14 +1,27 @@
<template> <template>
<n-message-provider>
<FlashMessages
:messages="props.flashMessages"
/>
</n-message-provider>
<div> <div>
Bin in Users Bin in Users
</div> </div>
<div>
{{ users }}
</div>
</template> </template>
<script setup> <script setup lang="ts">
import BELayout from '@/layouts/BELayout.vue' import BELayout from '@/layouts/BELayout.vue'
import MainNav from '@/components/MainNav.vue' import FlashMessages from '@/components/FlashMessages.vue'
defineOptions({ layout: BELayout }) defineOptions({ layout: BELayout })
const props = defineProps({
users: Object,
flashMessages: Object
})
</script> </script>

5
resources/js/vue-shim.d.ts vendored Normal file
View File

@@ -0,0 +1,5 @@
declare module '*.vue' {
import type { DefineComponent } from "vue"
const component: DefineComponent<{}, {}, any>
export default component
}

59
start/bouncer.ts Normal file
View File

@@ -0,0 +1,59 @@
/**
* Contract source: https://git.io/Jte3T
*
* Feel free to let us know via PR, if you find something broken in this config
* file.
*/
import Bouncer from '@ioc:Adonis/Addons/Bouncer'
/*
|--------------------------------------------------------------------------
| Bouncer Actions
|--------------------------------------------------------------------------
|
| Actions allows you to separate your application business logic from the
| authorization logic. Feel free to make use of policies when you find
| yourself creating too many actions
|
| You can define an action using the `.define` method on the Bouncer object
| as shown in the following example
|
| ```
| Bouncer.define('deletePost', (user: User, post: Post) => {
| return post.user_id === user.id
| })
| ```
|
|****************************************************************
| NOTE: Always export the "actions" const from this file
|****************************************************************
*/
export const { actions } = Bouncer
/*
|--------------------------------------------------------------------------
| Bouncer Policies
|--------------------------------------------------------------------------
|
| Policies are self contained actions for a given resource. For example: You
| can create a policy for a "User" resource, one policy for a "Post" resource
| and so on.
|
| The "registerPolicies" accepts a unique policy name and a function to lazy
| import the policy
|
| ```
| Bouncer.registerPolicies({
| UserPolicy: () => import('App/Policies/User'),
| PostPolicy: () => import('App/Policies/Post')
| })
| ```
|
|****************************************************************
| NOTE: Always export the "policies" const from this file
|****************************************************************
*/
export const { policies } = Bouncer.registerPolicies({
UserPolicy: () => import('App/Policies/UserPolicy')
})

View File

@@ -8,7 +8,6 @@
"build" "build"
], ],
"compilerOptions": { "compilerOptions": {
"allowJs": true,
"outDir": "build", "outDir": "build",
"rootDir": "./", "rootDir": "./",
"sourceMap": true, "sourceMap": true,
@@ -35,7 +34,8 @@
"@japa/preset-adonis/build/adonis-typings", "@japa/preset-adonis/build/adonis-typings",
"@eidellev/inertia-adonisjs", "@eidellev/inertia-adonisjs",
"@adonisjs/lucid", "@adonisjs/lucid",
"@adonisjs/auth" "@adonisjs/auth",
"@adonisjs/bouncer"
] ]
} }
} }

13
tsconfig.vue.json Normal file
View File

@@ -0,0 +1,13 @@
{
// tsconfig.vue.json
"extends": "@vue/tsconfig/tsconfig.json",
"include": [
"./resources/js/**/*"
],
"compilerOptions": {
"paths": {
"@/*": ["./resources/js/"]
}
}
}

View File

@@ -47,7 +47,7 @@ Encore.setPublicPath('/assets')
| entrypoints. | entrypoints.
| |
*/ */
Encore.addEntry('app', './resources/js/app.js') Encore.addEntry('app', './resources/js/app.ts')
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
@@ -187,6 +187,8 @@ Encore.enableVueLoader(() => {}, {
version: 3, version: 3,
runtimeCompilerBuild: false, runtimeCompilerBuild: false,
useJsx: false useJsx: false
}).enableTypeScriptLoader(config => {
config.configFile = 'tsconfig.vue.json'
}).addAliases({ }).addAliases({
'@': join(__dirname, 'resources/js') '@': join(__dirname, 'resources/js')
}).configureDefinePlugin(options => { }).configureDefinePlugin(options => {