Compare commits

..

6 Commits

Author SHA1 Message Date
Corentin Thomasset
433d6eae5b chore(release): 2.12.0 2022-08-24 00:17:40 +02:00
Corentin Thomasset
07a5fa51ec chore(release): 2.11.0 2022-08-24 00:16:59 +02:00
Corentin Thomasset
cc6070a166 feat(new-tool): added otp generator 2022-08-24 00:10:53 +02:00
Corentin Thomasset
741a3c25a9 feat(config): added tsx to allowed extension 2022-08-24 00:10:31 +02:00
Corentin Thomasset
a89c9bea42 refactor(useQRCode): switched args to MaybeRef 2022-08-24 00:09:59 +02:00
Corentin Thomasset
59ec6293b6 refactor: token generator can use a custom alphabet 2022-08-24 00:09:16 +02:00
18 changed files with 1073 additions and 19131 deletions

View File

@@ -2,29 +2,6 @@
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
## [2.13.0](https://github.com/CorentinTh/it-tools/compare/v2.11.0...v2.13.0) (2022-11-14)
### Features
* **config:** added tsx to allowed extension ([ea5e7a7](https://github.com/CorentinTh/it-tools/commit/ea5e7a7fc7df1a3a912193912a6ab80a8a36a256))
* **date-converter:** added mongodb objectID format ([4ef2588](https://github.com/CorentinTh/it-tools/commit/4ef25887b9d874b8789bf8dbabd8aab92b4b1b03))
* **new-tool:** added otp generator ([5f16885](https://github.com/CorentinTh/it-tools/commit/5f168859238e9c3a8b8bbaf6b550c4b9bd163e00))
* **new-tool:** mime type to extension converter ([7c9b8ac](https://github.com/CorentinTh/it-tools/commit/7c9b8ac178967151a4f921ac26e8c2fe8d23b886))
### Bug Fixes
* **ui:** remove icon transparency overlap ([35a3760](https://github.com/CorentinTh/it-tools/commit/35a376077116dd65b21f9a0786d2ecfc14db6051))
### Refactors
* **otp-generator:** changed url ([7f22995](https://github.com/CorentinTh/it-tools/commit/7f229959d64b7a932f32753e3838d87a819a9192))
* token generator can use a custom alphabet ([83da6b7](https://github.com/CorentinTh/it-tools/commit/83da6b7ee9db29e40faf288f9627257aa7124038))
* **ui:** change sponsor button location and caption ([5d8f46a](https://github.com/CorentinTh/it-tools/commit/5d8f46abf8d5a10cc4650efc87b12a9a6c537fe5))
* **useQRCode:** switched args to MaybeRef ([7de6c86](https://github.com/CorentinTh/it-tools/commit/7de6c86f9ead8d7315614cc508dfee4fed90e9c2))
## [2.12.0](https://github.com/CorentinTh/it-tools/compare/v2.10.3...v2.12.0) (2022-08-23)

17251
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"name": "it-tools",
"version": "2.13.0",
"version": "2.12.0",
"description": "Collection of handy online tools for developers, with great UX. ",
"keywords": [
"productivity",
@@ -36,53 +36,52 @@
"@vicons/material": "^0.12.0",
"@vicons/tabler": "^0.12.0",
"@vueuse/core": "^8.9.4",
"@vueuse/head": "^0.7.13",
"@vueuse/head": "^0.7.9",
"bcryptjs": "^2.4.3",
"change-case": "^4.1.2",
"colord": "^2.9.3",
"cron-validator": "^1.3.1",
"cronstrue": "^2.15.0",
"cronstrue": "^2.11.0",
"crypto-js": "^4.1.1",
"date-fns": "^2.29.3",
"date-fns": "^2.29.1",
"figue": "^1.2.0",
"highlight.js": "^11.6.0",
"json5": "^2.2.1",
"jwt-decode": "^3.1.2",
"lodash": "^4.17.21",
"mathjs": "^10.6.4",
"mime-types": "^2.1.35",
"naive-ui": "^2.33.5",
"pinia": "^2.0.23",
"naive-ui": "^2.32.1",
"pinia": "^2.0.18",
"plausible-tracker": "^0.3.8",
"qrcode": "^1.5.1",
"randombytes": "^2.1.0",
"sql-formatter": "^8.2.0",
"uuid": "^8.3.2",
"vue": "^3.2.45",
"vue-router": "^4.1.6"
"vue": "^3.2.37",
"vue-router": "^4.1.3"
},
"devDependencies": {
"@rushstack/eslint-patch": "^1.2.0",
"@rushstack/eslint-patch": "^1.1.4",
"@types/bcryptjs": "^2.4.2",
"@types/crypto-js": "^4.1.1",
"@types/jsdom": "^16.2.15",
"@types/lodash": "^4.14.188",
"@types/lodash": "^4.14.183",
"@types/mime-types": "^2.1.1",
"@types/node": "^16.18.3",
"@types/qrcode": "^1.5.0",
"@types/node": "^16.11.49",
"@types/qrcode": "^1.4.3",
"@types/randombytes": "^2.0.0",
"@types/uuid": "^8.3.4",
"@typescript-eslint/parser": "^5.42.1",
"@typescript-eslint/parser": "^5.33.1",
"@vitejs/plugin-vue": "^2.3.4",
"@vitejs/plugin-vue-jsx": "^1.3.10",
"@vue/eslint-config-prettier": "^7.0.0",
"@vue/eslint-config-typescript": "^10.0.0",
"@vue/test-utils": "^2.2.2",
"@vue/test-utils": "^2.0.2",
"@vue/tsconfig": "^0.1.3",
"c8": "^7.12.0",
"eslint": "^8.27.0",
"eslint": "^8.22.0",
"eslint-config-prettier": "^8.5.0",
"eslint-import-resolver-typescript": "^3.5.2",
"eslint-import-resolver-typescript": "^3.4.2",
"eslint-plugin-import": "^2.26.0",
"eslint-plugin-vue": "^8.7.1",
"jsdom": "^19.0.0",
@@ -94,7 +93,7 @@
"vite": "^2.9.15",
"vite-plugin-md": "^0.12.4",
"vite-plugin-pwa": "^0.11.13",
"vite-svg-loader": "^3.6.0",
"vite-svg-loader": "^3.4.0",
"vitest": "^0.13.1",
"vue-tsc": "^0.31.4",
"workbox-window": "^6.5.4"

2190
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -49,8 +49,7 @@ a {
}
.icon {
opacity: 0.6;
color: #ffffff;
opacity: 0.7;
}
.title {
@@ -58,8 +57,7 @@ a {
}
.description {
opacity: 0.6;
color: #ffffff;
opacity: 0.7;
margin: 5px 0;
}
}

View File

@@ -134,24 +134,23 @@ const menuOptions: MenuGroupOption[] = toolsByCategory.map((category) => ({
<search-bar />
<navbar-buttons v-if="!styleStore.isSmallScreen" />
<n-tooltip trigger="hover">
<template #trigger>
<n-button
round
type="primary"
tag="a"
href="https://github.com/sponsors/CorentinTh"
rel="noopener"
target="_blank"
>
Buy me a coffee
<n-icon v-if="!styleStore.isSmallScreen" :component="Heart" style="margin-left: 5px" />
<n-icon v-if="!styleStore.isSmallScreen" :component="Heart" style="margin-right: 5px" />
Sponsor
</n-button>
</template>
Support IT Tools development !
</n-tooltip>
<navbar-buttons v-if="!styleStore.isSmallScreen" />
</div>
<slot />
</template>

View File

@@ -53,7 +53,6 @@ import {
NTooltip,
NUpload,
NUploadDragger,
NPopover,
} from 'naive-ui';
const components = [
@@ -110,7 +109,6 @@ const components = [
NIcon,
NSwitch,
NCollapseTransition,
NPopover,
];
export const naive = create({ components });

View File

@@ -30,9 +30,4 @@ export const darkThemeOverrides: GlobalThemeOverrides = {
color: '#1e1e1e',
borderColor: 'transparent',
},
Table: {
tdColor: '#1e1e1e',
thColor: '#353535',
},
};

View File

@@ -132,10 +132,5 @@ const formats: Format[] = [
fromDate: (date) => date.toUTCString(),
toDate,
},
{
name: 'Mongo ObjectID',
fromDate: (date) => Math.floor(date.getTime() / 1000).toString(16) + '0000000000000000',
toDate: (objectId) => new Date(parseInt(objectId.substring(0, 8), 16) * 1000),
},
];
</script>

View File

@@ -1,8 +1,6 @@
import { LockOpen } from '@vicons/tabler';
import type { ToolCategory } from './tool';
import { tool as jwtParser } from './jwt-parser';
import { tool as mimeTypes } from './mime-types';
import { tool as otpCodeGeneratorAndValidator } from './otp-code-generator-and-validator';
import { tool as base64FileConverter } from './base64-file-converter';
import { tool as base64StringConverter } from './base64-string-converter';
@@ -67,8 +65,6 @@ export const toolsByCategory: ToolCategory[] = [
basicAuthGenerator,
metaTagGenerator,
otpCodeGeneratorAndValidator,
mimeTypes,
jwtParser,
],
},
{

View File

@@ -1,29 +0,0 @@
<template>
<n-space>
<em>{{ claim }}</em>
<span v-if="label.label !== claim">
<n-popover placement="right" trigger="hover">
<template #trigger>
<n-icon :component="InfoCircle" trigger />
</template>
{{ label.label }}
<template v-if="label.ref !== ''" #footer> {{ label.ref }} </template>
</n-popover>
</span>
</n-space>
</template>
<script setup lang="ts">
import { computed } from 'vue';
import { InfoCircle } from '@vicons/tabler';
import { get_claim_label } from './jwt-parser.service';
const props = defineProps({
claim: {
type: String,
default: '',
},
});
const label = computed(() => get_claim_label(props.claim ? props.claim : ''));
</script>

View File

@@ -1,11 +0,0 @@
import { ArrowsShuffle } from '@vicons/tabler';
import { defineTool } from '../tool';
export const tool = defineTool({
name: 'JWT parser',
path: '/jwt-parser',
description: '',
keywords: ['jwt', 'parser'],
component: () => import('./jwt-parser.vue'),
icon: ArrowsShuffle,
});

View File

@@ -1,429 +0,0 @@
import jwt_decode, { InvalidTokenError } from 'jwt-decode';
interface JWT {
header: Map<string, unknown>;
payload: Map<string, unknown>;
}
export function safe_jwt_decode(raw_jwt: string): JWT {
try {
const header = jwt_decode(raw_jwt, { header: true }) as Map<string, unknown>;
const payload = jwt_decode(raw_jwt) as Map<string, unknown>;
return { header: header, payload: payload };
} catch (e) {
if (e instanceof InvalidTokenError) {
return { header: new Map<string, unknown>(), payload: new Map<string, unknown>() };
} else {
throw e;
}
}
}
export function get_claim_label(claim: string): { label: string; ref: string } {
const infos = STANDARD_CLAIMS.find((info) => info.name === claim);
if (infos) {
return { label: infos.long_name, ref: infos.ref };
}
switch (claim) {
case 'typ':
return { label: 'Type', ref: '' };
case 'alg':
return { label: 'Algorithm', ref: '' };
}
return { label: claim, ref: '' };
}
export function parse_claim_value(claim: string, value: unknown): { value: unknown; extension?: unknown } {
switch (claim) {
case 'exp':
case 'nbf':
case 'iat': {
// Convert to milliseconds, JWT specs says it should be in seconds, JS
// works with milliseconds
value = typeof value === 'string' ? parseInt(value) : value;
const date = new Date((value as number) * 1000);
return { value: `${date.toLocaleDateString()} ${date.toLocaleTimeString()}`, extension: value };
}
case 'alg':
return { value: AlgorithmKeyDescriptionMapping[value as string], extension: value };
default:
if (typeof value === 'boolean') {
// Perhaps there's a better way to do this?
return { value: value ? 'true' : 'false' };
}
return { value: value };
}
}
// From https://datatracker.ietf.org/doc/html/rfc7518#section-3.1
const AlgorithmKeyDescriptionMapping: { [k: string]: string } = {
HS256: 'HMAC using SHA-256',
HS384: 'HMAC using SHA-384',
HS512: 'HMAC using SHA-512',
RS256: 'RSASSA-PKCS1-v1_5 using SHA-256',
RS384: 'RSASSA-PKCS1-v1_5 using SHA-384',
RS512: 'RSASSA-PKCS1-v1_5 using SHA-512',
ES256: 'ECDSA using P-256 and SHA-256',
ES384: 'ECDSA using P-384 and SHA-384',
ES512: 'ECDSA using P-521 and SHA-512',
PS256: 'RSASSA-PSS using SHA-256 and MGF1 with SHA-256',
PS384: 'RSASSA-PSS using SHA-384 and MGF1 with SHA-384',
PS512: 'RSASSA-PSS using SHA-512 and MGF1 with SHA-512',
none: 'No digital signature or MAC performed',
};
// List extracted from IANA: https://www.iana.org/assignments/jwt/jwt.xhtml
const STANDARD_CLAIMS = [
{
name: 'iss',
long_name: 'Issuer',
ref: '[RFC7519 - Section 4.1.1]',
},
{
name: 'sub',
long_name: 'Subject',
ref: '[RFC7519 - Section 4.1.2]',
},
{
name: 'aud',
long_name: 'Audience',
ref: '[RFC7519 - Section 4.1.3]',
},
{
name: 'exp',
long_name: 'Expiration Time',
ref: '[RFC7519 - Section 4.1.4]',
},
{
name: 'nbf',
long_name: 'Not Before',
ref: '[RFC7519 - Section 4.1.5]',
},
{
name: 'iat',
long_name: 'Issued At',
ref: '[RFC7519 - Section 4.1.6]',
},
{
name: 'jti',
long_name: 'JWT ID',
ref: '[RFC7519 - Section 4.1.7]',
},
{
name: 'name',
long_name: 'Full name',
ref: '[OpenID Connect Core 1.0 - Section 5.1]',
},
{
name: 'given_name',
long_name: 'Given name(s) or first name(s)',
ref: '[OpenID Connect Core 1.0 - Section 5.1]',
},
{
name: 'family_name',
long_name: 'Surname(s) or last name(s)',
ref: '[OpenID Connect Core 1.0 - Section 5.1]',
},
{
name: 'middle_name',
long_name: 'Middle name(s)',
ref: '[OpenID Connect Core 1.0 - Section 5.1]',
},
{
name: 'nickname',
long_name: 'Casual name',
ref: '[OpenID Connect Core 1.0 - Section 5.1]',
},
{
name: 'preferred_username',
long_name: 'Shorthand name by which the End-User wishes to be referred to',
ref: '[OpenID Connect Core 1.0 - Section 5.1]',
},
{
name: 'profile',
long_name: 'Profile page URL',
ref: '[OpenID Connect Core 1.0 - Section 5.1]',
},
{
name: 'picture',
long_name: 'Profile picture URL',
ref: '[OpenID Connect Core 1.0 - Section 5.1]',
},
{
name: 'website',
long_name: 'Web page or blog URL',
ref: '[OpenID Connect Core 1.0 - Section 5.1]',
},
{
name: 'email',
long_name: 'Preferred e-mail address',
ref: '[OpenID Connect Core 1.0 - Section 5.1]',
},
{
name: 'email_verified',
long_name: 'True if the e-mail address has been verified; otherwise false',
ref: '[OpenID Connect Core 1.0 - Section 5.1]',
},
{
name: 'gender',
long_name: 'Gender',
ref: '[OpenID Connect Core 1.0 - Section 5.1]',
},
{
name: 'birthdate',
long_name: 'Birthday',
ref: '[OpenID Connect Core 1.0 - Section 5.1]',
},
{
name: 'zoneinfo',
long_name: 'Time zone',
ref: '[OpenID Connect Core 1.0 - Section 5.1]',
},
{
name: 'locale',
long_name: 'Locale',
ref: '[OpenID Connect Core 1.0 - Section 5.1]',
},
{
name: 'phone_number',
long_name: 'Preferred telephone number',
ref: '[OpenID Connect Core 1.0 - Section 5.1]',
},
{
name: 'phone_number_verified',
long_name: 'True if the phone number has been verified; otherwise false',
ref: '[OpenID Connect Core 1.0 - Section 5.1]',
},
{
name: 'address',
long_name: 'Preferred postal address',
ref: '[OpenID Connect Core 1.0 - Section 5.1]',
},
{
name: 'updated_at',
long_name: 'Time the information was last updated',
ref: '[OpenID Connect Core 1.0 - Section 5.1]',
},
{
name: 'azp',
long_name: 'Authorized party - the party to which the ID Token was issued',
ref: '[OpenID Connect Core 1.0 - Section 2]',
},
{
name: 'nonce',
long_name: 'Value used to associate a Client session with an ID Token',
ref: '[OpenID Connect Core 1.0 - Section 2]',
},
{
name: 'auth_time',
long_name: 'Time when the authentication occurred',
ref: '[OpenID Connect Core 1.0 - Section 2]',
},
{
name: 'at_hash',
long_name: 'Access Token hash value',
ref: '[OpenID Connect Core 1.0 - Section 2]',
},
{
name: 'c_hash',
long_name: 'Code hash value',
ref: '[OpenID Connect Core 1.0 - Section 3.3.2.11]',
},
{
name: 'acr',
long_name: 'Authentication Context Class Reference',
ref: '[OpenID Connect Core 1.0 - Section 2]',
},
{
name: 'amr',
long_name: 'Authentication Methods References',
ref: '[OpenID Connect Core 1.0 - Section 2]',
},
{
name: 'sub_jwk',
long_name: 'Public key used to check the signature of an ID Token',
ref: '[OpenID Connect Core 1.0 - Section 7.4]',
},
{
name: 'cnf',
long_name: 'Confirmation',
ref: '[RFC7800 - Section 3.1]',
},
{
name: 'sip_from_tag',
long_name: 'SIP From tag header field parameter value',
ref: '[RFC8055][RFC3261]',
},
{
name: 'sip_date',
long_name: 'SIP Date header field value',
ref: '[RFC8055][RFC3261]',
},
{
name: 'sip_callid',
long_name: 'SIP Call-Id header field value',
ref: '[RFC8055][RFC3261]',
},
{
name: 'sip_cseq_num',
long_name: 'SIP CSeq numeric header field parameter value',
ref: '[RFC8055][RFC3261]',
},
{
name: 'sip_via_branch',
long_name: 'SIP Via branch header field parameter value',
ref: '[RFC8055][RFC3261]',
},
{
name: 'orig',
long_name: 'Originating Identity String',
ref: '[RFC8225 - Section 5.2.1]',
},
{
name: 'dest',
long_name: 'Destination Identity String',
ref: '[RFC8225 - Section 5.2.1]',
},
{
name: 'mky',
long_name: 'Media Key Fingerprint String',
ref: '[RFC8225 - Section 5.2.2]',
},
{
name: 'events',
long_name: 'Security Events',
ref: '[RFC8417 - Section 2.2]',
},
{
name: 'toe',
long_name: 'Time of Event',
ref: '[RFC8417 - Section 2.2]',
},
{
name: 'txn',
long_name: 'Transaction Identifier',
ref: '[RFC8417 - Section 2.2]',
},
{
name: 'rph',
long_name: 'Resource Priority Header Authorization',
ref: '[RFC8443 - Section 3]',
},
{
name: 'sid',
long_name: 'Session ID',
ref: '[OpenID Connect Front-Channel Logout 1.0 - Section 3]',
},
{
name: 'vot',
long_name: 'Vector of Trust value',
ref: '[RFC8485]',
},
{
name: 'vtm',
long_name: 'Vector of Trust trustmark URL',
ref: '[RFC8485]',
},
{
name: 'attest',
long_name: 'Attestation level as defined in SHAKEN framework',
ref: '[RFC8588]',
},
{
name: 'origid',
long_name: 'Originating Identifier as defined in SHAKEN framework',
ref: '[RFC8588]',
},
{
name: 'act',
long_name: 'Actor',
ref: '[RFC8693 - Section 4.1]',
},
{
name: 'scope',
long_name: 'Scope Values',
ref: '[RFC8693 - Section 4.2]',
},
{
name: 'client_id',
long_name: 'Client Identifier',
ref: '[RFC8693 - Section 4.3]',
},
{
name: 'may_act',
long_name: 'Authorized Actor - the party that is authorized to become the actor',
ref: '[RFC8693 - Section 4.4]',
},
{
name: 'jcard',
long_name: 'jCard data',
ref: '[RFC8688][RFC7095]',
},
{
name: 'at_use_nbr',
long_name: 'Number of API requests for which the access token can be used',
ref: '[ETSI GS NFV-SEC 022 V2.7.1]',
},
{
name: 'div',
long_name: 'Diverted Target of a Call',
ref: '[RFC8946]',
},
{
name: 'opt',
long_name: 'Original PASSporT (in Full Form)',
ref: '[RFC8946]',
},
{
name: 'vc',
long_name: 'Verifiable Credential as specified in the W3C Recommendation',
ref: '[W3C Recommendation Verifiable Credentials Data Model 1.0 - Expressing verifiable information on the Web (19 November 2019) - Section 6.3.1]',
},
{
name: 'vp',
long_name: 'Verifiable Presentation as specified in the W3C Recommendation',
ref: '[W3C Recommendation Verifiable Credentials Data Model 1.0 - Expressing verifiable information on the Web (19 November 2019) - Section 6.3.1]',
},
{
name: 'sph',
long_name: 'SIP Priority header field',
ref: '[RFC9027]',
},
{
name: 'ace_profile',
long_name: 'The ACE profile a token is supposed to be used with.',
ref: '[RFC-ietf-ace-oauth-authz-46 - Section 5.10]',
},
{
name: 'cnonce',
long_name:
'client-nonce. A nonce previously provided to the AS by the RS via the client. Used to verify token freshness when the RS cannot synchronize its clock with the AS.',
ref: '[RFC-ietf-ace-oauth-authz-46 - Section 5.10]',
},
{
name: 'exi',
long_name:
'Expires in. Lifetime of the token in seconds from the time the RS first sees it. Used to implement a weaker from of token expiration for devices that cannot synchronize their internal clocks.',
ref: '[RFC-ietf-ace-oauth-authz-46 - Section 5.10.3]',
},
{
name: 'roles',
long_name: 'Roles',
ref: '[RFC7643 - Section 4.1.2][RFC9068 - Section 2.2.3.1]',
},
{
name: 'groups',
long_name: 'Groups',
ref: '[RFC7643 - Section 4.1.2][RFC9068 - Section 2.2.3.1]',
},
{
name: 'entitlements',
long_name: 'Entitlements',
ref: '[RFC7643 - Section 4.1.2][RFC9068 - Section 2.2.3.1]',
},
{
name: 'token_introspection',
long_name: 'Token introspection response',
ref: '[RFC-ietf-oauth-jwt-introspection-response-12 - Section 5]',
},
];

View File

@@ -1,69 +0,0 @@
<template>
<n-card>
<n-form-item label="JWT to decode" :feedback="validation.message" :validation-status="validation.status">
<n-input v-model:value="raw_jwt" type="textarea" placeholder="Put your token here..." rows="5" />
</n-form-item>
<n-form-item label="Display parsed value?" label-placement="left" :show-feedback="false">
<n-switch v-model:value="showParsedValues" />
</n-form-item>
<n-table>
<tbody>
<td colspan="2" class="table-header"><strong>Header</strong></td>
<tr v-for="[key, value] in Object.entries(decodedJWT.header)" :key="key">
<td class="claims"><claim-vue :claim="key" /></td>
<td>
<span v-if="!showParsedValues">{{ value }}</span>
<value-vue v-else :claim="key" :value="value" />
</td>
</tr>
<td colspan="2" class="table-header"><strong>Payload</strong></td>
<tr v-for="[key, value] in Object.entries(decodedJWT.payload)" :key="key">
<td class="claims"><claim-vue :claim="key" /></td>
<td>
<span v-if="!showParsedValues">{{ value }}</span>
<value-vue v-else :claim="key" :value="value" />
</td>
</tr>
</tbody>
</n-table>
</n-card>
</template>
<script setup lang="ts">
import { computed, ref } from 'vue';
import jwt_decode from 'jwt-decode';
import { useValidation } from '@/composable/validation';
import { isNotThrowing } from '@/utils/boolean';
import { safe_jwt_decode } from './jwt-parser.service';
import claimVue from './claim.vue';
import valueVue from './value.vue';
const raw_jwt = ref(
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c',
);
const showParsedValues = ref(true);
const decodedJWT = computed(() => {
return safe_jwt_decode(raw_jwt.value);
});
const validation = useValidation({
source: raw_jwt,
rules: [
{
validator: (value) => value.length > 0 && isNotThrowing(() => jwt_decode(value, { header: true })),
message: 'Invalid JWT',
},
],
});
</script>
<style lang="less" scoped>
.table-header {
text-align: center;
}
.claims {
width: 20%;
}
</style>

View File

@@ -1,24 +0,0 @@
<template>
<n-space>
{{ value.value }}
<em v-if="value.extension">({{ value.extension }})</em>
</n-space>
</template>
<script setup lang="ts">
import { computed } from 'vue';
import { parse_claim_value } from './jwt-parser.service';
const props = defineProps({
claim: {
type: String,
default: '',
},
value: {
type: String,
default: '',
},
});
const value = computed(() => parse_claim_value(props.claim, props.value));
</script>

View File

@@ -1,11 +0,0 @@
import { World } from '@vicons/tabler';
import { defineTool } from '../tool';
export const tool = defineTool({
name: 'Mime types',
path: '/mime-types',
description: 'Convert mime types to extensions and vice-versa.',
keywords: ['mime', 'types', 'extension', 'content', 'type'],
component: () => import('./mime-types.vue'),
icon: World,
});

View File

@@ -1,99 +0,0 @@
<template>
<n-card>
<n-h2 style="margin-bottom: 0">Mime type to extension</n-h2>
<div style="opacity: 0.8">Now witch file extensions are associated to a mime-type</div>
<n-form-item>
<n-select
v-model:value="selectedMimeType"
filterable
:options="mimeToExtensionsOptions"
size="large"
placeholder="Select your mimetype here... (ex: application/pdf)"
/>
</n-form-item>
<div v-if="extensionsFound.length > 0">
Extensions of files with the <n-tag round :bordered="false">{{ selectedMimeType }}</n-tag> mime-type:
<div style="margin-top: 10px">
<n-tag
v-for="extension of extensionsFound"
:key="extension"
round
:bordered="false"
type="primary"
style="margin-right: 10px"
>
.{{ extension }}
</n-tag>
</div>
</div>
</n-card>
<n-card>
<n-h2 style="margin-bottom: 0">File extension to mime type</n-h2>
<div style="opacity: 0.8">Now witch mime type is associated to a file extension</div>
<n-form-item>
<n-select
v-model:value="selectedExtension"
filterable
:options="extensionToMimeTypeOptions"
size="large"
placeholder="Select your mimetype here... (ex: application/pdf)"
/>
</n-form-item>
<div v-if="selectedExtension">
Mime type associated to the extension <n-tag round :bordered="false">{{ selectedExtension }}</n-tag> file
extension:
<div style="margin-top: 10px">
<n-tag round :bordered="false" type="primary" style="margin-right: 10px">
{{ mimeTypeFound }}
</n-tag>
</div>
</div>
</n-card>
<div>
<n-table>
<thead>
<tr>
<th>Mime types</th>
<th>Extensions</th>
</tr>
</thead>
<tbody>
<tr v-for="{ mimeType, extensions } of mimeInfos" :key="mimeType">
<td>{{ mimeType }}</td>
<td>
<n-tag v-for="extension of extensions" :key="extension" round :bordered="false" style="margin-right: 10px">
.{{ extension }}
</n-tag>
</td>
</tr>
</tbody>
</n-table>
</div>
</template>
<script setup lang="ts">
import { types as extensionToMimeType, extensions as mimeTypeToExtension } from 'mime-types';
import { computed, ref } from 'vue';
const mimeInfos = Object.entries(mimeTypeToExtension).map(([mimeType, extensions]) => ({ mimeType, extensions }));
const mimeToExtensionsOptions = Object.keys(mimeTypeToExtension).map((label) => ({ label, value: label }));
const selectedMimeType = ref(undefined);
const extensionsFound = computed(() => (selectedMimeType.value ? mimeTypeToExtension[selectedMimeType.value] : []));
const extensionToMimeTypeOptions = Object.keys(extensionToMimeType).map((label) => {
const extension = `.${label}`;
return { label: extension, value: label };
});
const selectedExtension = ref(undefined);
const mimeTypeFound = computed(() => (selectedExtension.value ? extensionToMimeType[selectedExtension.value] : []));
</script>
<style lang="less" scoped></style>

View File

@@ -3,7 +3,7 @@ import { defineTool } from '../tool';
export const tool = defineTool({
name: 'OTP code generator',
path: '/otp-generator',
path: '/otp-code-generator-and-validator',
description: 'Generate and validate time-based OTP (one time password) for multi-factor authentication.',
keywords: [
'otp',