-
Notifications
You must be signed in to change notification settings - Fork 92
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
This commit adds functionality for handling API Keys. It includes the following changes: - Added `apikeys_api` namespace to the API routes - Implemented routes for getting all API Keys, creating a new API Key, getting a single API Key, and deleting a single API Key - Created a new model `APIKeys` to handle API Key data in the database - Updated the `security.py` file to check if a token is related to an API Key when verifying if it is revoked - Added new components `APIKeyItem`, `APIKeyList`, and `APIKeyForm` to support displaying API Keys and creating new ones - Added new API Key-related Vue files to the frontend: - `APIKeyList.vue` displays a list of API Keys - `APIKeyItem.vue` represents an individual API Key item in the list - `APIKeyForm.vue` allows users to create a new API Key Related: #1234
- Loading branch information
1 parent
ad73f3e
commit 1a42e64
Showing
12 changed files
with
348 additions
and
7 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
from flask import request | ||
from flask_jwt_extended import jwt_required, current_user, create_access_token, get_jti | ||
from flask_restx import Namespace, Resource | ||
from app.models.database.api_keys import APIKeys | ||
from json import loads, dumps | ||
from playhouse.shortcuts import model_to_dict | ||
from datetime import datetime | ||
|
||
api = Namespace('API Keys', description='API Keys related operations', path='/apikeys') | ||
|
||
@api.route("") | ||
class APIKeysListAPI(Resource): | ||
|
||
method_decorators = [jwt_required()] | ||
|
||
@api.doc(description="Get all API Keys") | ||
@api.response(200, "Successfully retrieved all API Keys") | ||
def get(self): | ||
"""Get all API Keys""" | ||
response = list(APIKeys.select().where(APIKeys.user == current_user['id']).dicts()) | ||
return loads(dumps(response, indent=4, sort_keys=True, default=str)), 200 | ||
|
||
@api.doc(description="Create an API Key") | ||
@api.response(200, "Successfully created an API Key") | ||
def post(self): | ||
"""Create an API Key""" | ||
token = create_access_token(fresh=False, identity=current_user['id'], expires_delta=False) | ||
jti = get_jti(encoded_token=token) | ||
|
||
api_key = APIKeys.create( | ||
name=str(request.form.get("name")), | ||
key=str(token), | ||
jti=str(jti), | ||
user=current_user['id'] | ||
) | ||
|
||
response = model_to_dict(APIKeys.get(APIKeys.id == api_key)) | ||
return loads(dumps(response, indent=4, sort_keys=True, default=str)), 200 | ||
|
||
@api.route("/<int:api_key_id>") | ||
class APIKeysAPI(Resource): | ||
|
||
method_decorators = [jwt_required()] | ||
|
||
@api.doc(description="Get a single API Key") | ||
@api.response(200, "Successfully retrieved API Key") | ||
def get(self, api_key_id): | ||
"""Get a single API Key""" | ||
api_key = APIKeys.get_or_none(APIKeys.id == api_key_id and APIKeys.user == current_user['id']) | ||
if not api_key: | ||
return {"message": "API Key not found"}, 404 | ||
return loads(dumps(model_to_dict(api_key), indent=4, sort_keys=True, default=str)), 200 | ||
|
||
@api.doc(description="Delete a single API Key") | ||
@api.response(200, "Successfully deleted API Key") | ||
def delete(self, api_key_id): | ||
"""Delete a single API Key""" | ||
api_key = APIKeys.get_or_none(APIKeys.id == api_key_id and APIKeys.user == current_user['id']) | ||
if not api_key: | ||
return {"message": "API Key not found"}, 404 | ||
api_key.delete_instance() | ||
return {"message": "API Key deleted"}, 200 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
<template> | ||
<ListItem icon="fa-key"> | ||
<template #title> | ||
<span class="text-lg">{{ apikey.name }}</span> | ||
<div class="flex flex-col"> | ||
<!-- <p v-else class="text-xs truncate text-gray-500 dark:text-gray-400 w-full">No email</p> --> | ||
<p class="text-xs truncate text-gray-500 dark:text-gray-400 w-full">{{ $filter("timeAgo", apikey.created) }}</p> | ||
</div> | ||
</template> | ||
<template #buttons> | ||
<div class="flex flex-row space-x-2"> | ||
<button @click="localDeleteAPIKey" :disabled="disabled.delete" class="bg-red-600 hover:bg-primary_hover focus:outline-none text-white font-medium rounded px-3.5 py-2 text-sm dark:bg-red-600 dark:hover:bg-primary_hover"> | ||
<i class="fa-solid fa-trash"></i> | ||
</button> | ||
</div> | ||
</template> | ||
</ListItem> | ||
</template> | ||
|
||
<script lang="ts"> | ||
import { defineComponent } from "vue"; | ||
import { mapActions } from "pinia"; | ||
import { useAPIKeyStore } from "@/stores/apikeys"; | ||
import type { APIKey } from "@/types/api/apikeys"; | ||
import ListItem from "../ListItem.vue"; | ||
export default defineComponent({ | ||
name: "UserItem", | ||
components: { | ||
ListItem, | ||
}, | ||
props: { | ||
apikey: { | ||
type: Object as () => APIKey, | ||
required: true, | ||
}, | ||
}, | ||
data() { | ||
return { | ||
disabled: { | ||
delete: false, | ||
}, | ||
}; | ||
}, | ||
methods: { | ||
async localDeleteAPIKey() { | ||
if (await this.$modal.confirmModal(this.__("Are you sure?"), this.__("Are you sure you want to delete this API key?"))) { | ||
this.disabled.delete = true; | ||
await this.deleteAPIKey(this.apikey.id).finally(() => (this.disabled.delete = false)); | ||
this.$toast.info(this.__("API key deleted successfully")); | ||
} else { | ||
this.$toast.info(this.__("API key deletion cancelled")); | ||
} | ||
}, | ||
...mapActions(useAPIKeyStore, ["deleteAPIKey"]), | ||
}, | ||
}); | ||
</script> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
<template> | ||
<Draggable v-if="apikeys && apikeys.length > 0" v-model="apikeys" tag="ul" group="apikeys" ghost-class="moving-card" :animation="200" item-key="id"> | ||
<template #item="{ element }"> | ||
<li class="mb-2"> | ||
<APIKeyItem :apikey="element" /> | ||
</li> | ||
</template> | ||
</Draggable> | ||
<div v-else class="flex flex-col justify-center items-center space-y-1"> | ||
<i class="fa-solid fa-info-circle text-3xl text-gray-400"></i> | ||
<span class="text-gray-400">{{ __("No API Keys found") }}</span> | ||
</div> | ||
</template> | ||
|
||
<script lang="ts"> | ||
import { defineComponent } from "vue"; | ||
import { useAPIKeyStore } from "@/stores/apikeys"; | ||
import { mapActions, mapWritableState } from "pinia"; | ||
import Draggable from "vuedraggable"; | ||
import APIKeyItem from "./APIKeyItem.vue"; | ||
export default defineComponent({ | ||
name: "APIKeyList", | ||
components: { | ||
Draggable, | ||
APIKeyItem, | ||
}, | ||
computed: { | ||
...mapWritableState(useAPIKeyStore, ["apikeys"]), | ||
}, | ||
methods: { | ||
...mapActions(useAPIKeyStore, ["getAPIKeys"]), | ||
}, | ||
async created() { | ||
await this.getAPIKeys(); | ||
}, | ||
}); | ||
</script> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
<template> | ||
<Transition name="fade" mode="out-in" :duration="{ enter: 300, leave: 300 }"> | ||
<template v-if="newAPIKey === null"> | ||
<FormKit v-model="apikey" @submit="localCreateAPIKey" type="form" :classes="{ input: '!bg-secondary' }" :submit-label="__('Create API Key')" :submit-attrs="{ wrapperClass: 'flex justify-end' }"> | ||
<FormKit typ="text" name="name" validation="required|alpha_spaces:latin" :label="__('Name')" :placeholder="__('My API Key')" /> | ||
</FormKit> | ||
</template> | ||
<template v-else> | ||
<div class="flex flex-col"> | ||
<div class="text-sm mb-4"> | ||
{{ __("Please take a copy your API key. You will not be able to see it again, please make sure to store it somewhere safe.") }} | ||
</div> | ||
<FormKit type="text" :value="newAPIKey ?? 'Unknown'" :label="__('API Key')" :disabled="true" :classes="{ input: '!w-full' }" /> | ||
<FormKit type="button" @click="copyAPIKey" :classes="{ input: '!bg-secondary', wrapper: 'flex justify-end' }"> | ||
{{ __("Copy") }} | ||
</FormKit> | ||
</div> | ||
</template> | ||
</Transition> | ||
</template> | ||
|
||
<script lang="ts"> | ||
import { defineComponent } from "vue"; | ||
import { useAPIKeyStore } from "@/stores/apikeys"; | ||
import { mapActions } from "pinia"; | ||
import { useClipboard } from "@vueuse/core"; | ||
export default defineComponent({ | ||
name: "WebhookForm", | ||
data() { | ||
return { | ||
useClipboard: useClipboard(), | ||
newAPIKey: null as string | null, | ||
apikey: { name: "" }, | ||
}; | ||
}, | ||
methods: { | ||
copyAPIKey() { | ||
this.useClipboard.copy(this.newAPIKey ?? ""); | ||
this.$toast.info(this.__("Copied to clipboard")); | ||
}, | ||
async localCreateAPIKey() { | ||
const apikey = await this.createAPIKey(this.apikey); | ||
this.newAPIKey = apikey?.key ?? null; | ||
}, | ||
...mapActions(useAPIKeyStore, ["createAPIKey"]), | ||
}, | ||
}); | ||
</script> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
<template> | ||
<APIKeyList /> | ||
<div class="fixed right-6 bottom-6 group"> | ||
<FormKit type="button" :classes="{ input: '!w-14 !h-14' }" @click="createAPIKey"> | ||
<i class="fas fa-plus text-xl transition-transform group-hover:rotate-45"></i> | ||
</FormKit> | ||
</div> | ||
</template> | ||
|
||
<script lang="ts"> | ||
import { defineComponent } from "vue"; | ||
import APIKeyList from "@/components/APIKeyList/APIKeyList.vue"; | ||
import APIKeyForm from "@/components/Forms/APIKeyForm.vue"; | ||
export default defineComponent({ | ||
name: "APIKeysView", | ||
components: { | ||
APIKeyList, | ||
}, | ||
methods: { | ||
createAPIKey() { | ||
this.$modal.openModal(APIKeyForm, { | ||
title: this.__("Create API Key"), | ||
disableFooter: true, | ||
}); | ||
}, | ||
}, | ||
}); | ||
</script> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.