Skip to content

Commit bb79f99

Browse files
authored
Add nice to have features and refactor to doesn't use dynamic delete (#150)
* chore: update add dependencies * feat(apps): add eslint-plugin-tailwindcss * feat(apps): improve linting and lint code * feat(apps): refactor to doesn't use dynamic delete * fix(apps): check has wrong type * fix(apps): error messages are shown even if no error is there * feat(apps): implement otp formkit input * feat(api): enable 2fa in fortify * feat(apps): prepare 2fa implementation * feat(app): improve svg style of 2fa qrcode * feat(apps): dev version of 2fa confirm * feat(apps): 2fa settings implementation * feat(apps): add autocomplete attributes * fix(apps): add missing .value * feat(apps): first dev version of two factor challenge * feat(apps): implement 2fa challenge * feat(api): remove name from users * feat(apps): clean up code and add final implemenation of 2fa * feat(github): add breaking changes to release * fix(apps): fix types * fix(apps): auth redirect not working correct * feat(api): implement placeholder feature and api routes * feat(apps): implement feature flags in apps * fix(apps): add missing types * fix(apps): fix usage of wrong type * feat(apps): implement turnstile * feat(api): implement turnstile * feat(apps): add more callbacks * feat: implement turnstile on login * fix(apps): fix wrong redirect * feat(apps): improve redirect param * feat(apps): remove unused READMEs * feat: implement turnstile and update docs * feat(api): first basic boilerplate for auth providers * fix(api): remove old attribute * feat(api): improve migrations * feat(api): remove dev routes * feat: first final implementation of auth providers and small code improvements * chore: lint and format * chore: update dependencies * feat(apps): disable vue/html-self-closing * chore: lint and format * fix(api): update test to health check route * fix(api): use up instead of health route * chore: update dependencies
1 parent f70084a commit bb79f99

File tree

120 files changed

+10704
-6049
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

120 files changed

+10704
-6049
lines changed

.env.example

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,4 @@
1-
# Docker
1+
#######################################################
2+
# Docker compose #
3+
#######################################################
24
COMPOSE_PROJECT_NAME="saas-template"

.github/release.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ changelog:
77
- "question"
88
- "wontfix"
99
categories:
10+
- title: Breaking changes
11+
labels:
12+
- "⚠️ breaking change"
1013
- title: Added
1114
labels:
1215
- "enhancement"

README.md

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,19 +43,25 @@ You need to set the following secrets in the GitHub repository.
4343
- `STAGE_SERVICE_API_LARAVEL_FORGE_TRIGGER_URL` - The Laravel Forge trigger URL for the stage environment.
4444
- `PROD_SERVICE_API_LARAVEL_FORGE_TRIGGER_URL` - The Laravel Forge trigger URL for the prod environment.
4545
- `NUXT_UI_PRO_LICENSE` - The Nuxt UI Pro license key (opt-out if not using Nuxt UI Pro).
46-
- `FORMKIT_PRO_KEY` - The FormKit Pro key (opt-in if using FormKit Pro (search for `#NOTE: FORMKIT PRO` in the code)).
46+
- `NUXT_PUBLIC_FORMKIT_PRO_KEY` - The FormKit Pro key (opt-in if using FormKit Pro (search for `#NOTE: FORMKIT PRO` in the code)).
4747

4848
#### Dependabot secrets
4949

5050
- `NUXT_UI_PRO_LICENSE` - The Nuxt UI Pro license key (opt-out if not using Nuxt UI Pro).
51-
- `FORMKIT_PRO_KEY` - The FormKit Pro key (opt-in if using FormKit Pro (search for `#NOTE: FORMKIT PRO` in the code)).
51+
- `NUXT_PUBLIC_FORMKIT_PRO_KEY` - The FormKit Pro key (opt-in if using FormKit Pro (search for `#NOTE: FORMKIT PRO` in the code)).
5252

5353
### Cloudflare Pages
5454

5555
You need to create a Cloudflare page per app and set the following environment variables.
5656

57+
- `NUXT_PUBLIC_API_URL` - The API URL for the app.
5758
- `NUXT_UI_PRO_LICENSE` - The Nuxt UI Pro license key (opt-out if not using Nuxt UI Pro).
58-
- `FORMKIT_PRO_KEY` - The FormKit Pro key (opt-in if using FormKit Pro (search for `#NOTE: FORMKIT PRO` in the code)).
59+
- `NUXT_PUBLIC_FORMKIT_PRO_KEY` - The FormKit Pro key (opt-in if using FormKit Pro (search for `#NOTE: FORMKIT PRO` in the code)).
60+
- `NUXT_PUBLIC_TURNSTILE_SITE_KEY` - The Turnstile site key for the app.
61+
- `NUXT_TURNSTILE_SECRET_KEY` - The Turnstile secret key for the app.
62+
- `NUXT_PUBLIC_APP_NAME` - The app name for the app.
63+
- `NUXT_PUBLIC_APP_VERSION` - The app version for the app. (should be set by the deployment)
64+
- `NUXT_PUBLIC_BASE_URL` - The base URL for the app.
5965

6066
### Laravel Forge
6167

apps/blog/.env.example

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,27 @@
1+
#######################################################
2+
# API #
3+
#######################################################
4+
NUXT_PUBLIC_API_URL="http://localhost:8000"
5+
6+
#######################################################
7+
# Nuxt UI #
8+
#######################################################
19
NUXT_UI_PRO_LICENSE="<your-nuxt-ui-license>"
2-
FORMKIT_PRO_KEY="<your-formkit-key>"
3-
API_URL="http://localhost:8000"
10+
11+
#######################################################
12+
# Formkit #
13+
#######################################################
14+
NUXT_PUBLIC_FORMKIT_PRO_KEY="<your-formkit-key>"
15+
16+
#######################################################
17+
# Turnstile #
18+
#######################################################
19+
NUXT_PUBLIC_TURNSTILE_SITE_KEY="<your-turnstile-site-key>"
20+
NUXT_TURNSTILE_SECRET_KEY="<your-turnstile-secret-key>"
21+
22+
#######################################################
23+
# General #
24+
#######################################################
25+
NUXT_PUBLIC_APP_NAME="@tituskirch/app-base"
26+
# NUXT_PUBLIC_APP_VERSION=(should be set by the deployment)
27+
NUXT_PUBLIC_BASE_URL="http://localhost:3001"

apps/blog/eslint.config.mjs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,2 @@
1-
import withNuxt from './.nuxt/eslint.config.mjs';
2-
3-
export default withNuxt();
1+
import eslintConfig from '@tituskirch/app-base/eslint.config.mjs';
2+
export default eslintConfig;

apps/blog/package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@
1717
"type": "module",
1818
"dependencies": {
1919
"@tituskirch/app-base": "workspace:*",
20-
"nuxt": "^3.11.2",
21-
"vue": "^3.4.27",
22-
"vue-router": "^4.3.2"
20+
"nuxt": "^3.12.2",
21+
"vue": "^3.4.30",
22+
"vue-router": "^4.4.0"
2323
}
2424
}

apps/web/.env.example

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,27 @@
1+
#######################################################
2+
# API #
3+
#######################################################
4+
NUXT_PUBLIC_API_URL="http://localhost:8000"
5+
6+
#######################################################
7+
# Nuxt UI #
8+
#######################################################
19
NUXT_UI_PRO_LICENSE="<your-nuxt-ui-license>"
2-
FORMKIT_PRO_KEY="<your-formkit-key>"
3-
API_URL="http://localhost:8000"
10+
11+
#######################################################
12+
# Formkit #
13+
#######################################################
14+
NUXT_PUBLIC_FORMKIT_PRO_KEY="<your-formkit-key>"
15+
16+
#######################################################
17+
# Turnstile #
18+
#######################################################
19+
NUXT_PUBLIC_TURNSTILE_SITE_KEY="<your-turnstile-site-key>"
20+
NUXT_TURNSTILE_SECRET_KEY="<your-turnstile-secret-key>"
21+
22+
#######################################################
23+
# General #
24+
#######################################################
25+
NUXT_PUBLIC_APP_NAME="@tituskirch/app-base"
26+
# NUXT_PUBLIC_APP_VERSION=(should be set by the deployment)
27+
NUXT_PUBLIC_BASE_URL="http://localhost:3000"

apps/web/README.md

Lines changed: 0 additions & 75 deletions
This file was deleted.
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
<script setup lang="ts">
2+
const { me } = useUser();
3+
const user = await me();
4+
5+
// disable two factor authentication
6+
const { refetchMe } = useUser();
7+
const { disableTwoFactorAuthentication } = useAuth();
8+
const {
9+
status: disableTwoFactorAuthenticationStatus,
10+
execute: disableTwoFactorAuthenticationExecute,
11+
} = await disableTwoFactorAuthentication();
12+
const disableTwoFactorAuthenticationClick = async () => {
13+
await disableTwoFactorAuthenticationExecute();
14+
await refetchMe();
15+
};
16+
</script>
17+
18+
<template>
19+
<AuthNeedsToConfirmUserPasswordButton
20+
v-if="user?.twoFactorConfirmedAt"
21+
:confirm-password-button-title="
22+
$t('auth.disableTwoFactorAuthenticationButton.action.confirmPasswordAndDisable.label')
23+
"
24+
:confirm-password-button-props="{ block: true }"
25+
:confirm-password-button-callback="disableTwoFactorAuthenticationClick"
26+
>
27+
<UButton
28+
block
29+
icon="i-fa6-solid-unlock"
30+
:loading="disableTwoFactorAuthenticationStatus === 'pending'"
31+
color="red"
32+
@click="disableTwoFactorAuthenticationClick"
33+
>
34+
{{ $t('auth.disableTwoFactorAuthenticationButton.action.disable.label') }}
35+
</UButton>
36+
</AuthNeedsToConfirmUserPasswordButton>
37+
</template>
Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
<script setup lang="ts">
2+
const emit = defineEmits<{
3+
success: [];
4+
}>();
5+
6+
// get two factor qr code
7+
const { enableTwoFactorAuthentication, twoFactorQrCode } = useAuth();
8+
9+
const {
10+
status: enableTwoFactorAuthenticationStatus,
11+
execute: enableTwoFactorAuthenticationExecute,
12+
} = await enableTwoFactorAuthentication();
13+
const enableTwoFactorAuthenticationClick = async () => {
14+
await enableTwoFactorAuthenticationExecute();
15+
};
16+
const {
17+
data: twoFactorQrCodeData,
18+
status: twoFactorQrCodeStatus,
19+
execute: twoFactorQrCodeExecute,
20+
} = await twoFactorQrCode();
21+
watch(
22+
() => enableTwoFactorAuthenticationStatus.value,
23+
async (status) => {
24+
if (status === 'success') {
25+
await twoFactorQrCodeExecute();
26+
}
27+
}
28+
);
29+
const twoFactorQrCodeSecret = computed(() => {
30+
return twoFactorQrCodeData.value?.url?.split('?secret=')[1]?.split('&')[0];
31+
});
32+
33+
// form setup
34+
const form: Ref<AuthUserConfirmedTwoFactorAuthenticationData> = ref({
35+
code: '',
36+
});
37+
const { confirmedTwoFactorAuthentication } = useAuth();
38+
const { refetchMe } = useUser();
39+
const {
40+
status: confirmedTwoFactorAuthenticationStatus,
41+
execute: confirmedTwoFactorAuthenticationExecute,
42+
error: confirmedTwoFactorAuthenticationError,
43+
} = await confirmedTwoFactorAuthentication({
44+
data: form,
45+
});
46+
const { submit: submitConfirmedTwoFactorAuthentication, errorMessages } =
47+
useFormKitForm<AuthUserConfirmedTwoFactorAuthenticationData>({
48+
form,
49+
error: confirmedTwoFactorAuthenticationError,
50+
status: confirmedTwoFactorAuthenticationStatus,
51+
executeCallback: confirmedTwoFactorAuthenticationExecute,
52+
successCallback: async () => {
53+
emit('success');
54+
await refetchMe();
55+
},
56+
});
57+
</script>
58+
59+
<template>
60+
<AuthNeedsToConfirmUserPasswordButton
61+
v-if="!twoFactorQrCodeData?.svg"
62+
:confirm-password-button-title="
63+
$t('auth.enableTwoFactorAuthenticationForm.action.confirmPasswordAndEnable.label')
64+
"
65+
:confirm-password-button-props="{ block: true }"
66+
:confirm-password-button-callback="enableTwoFactorAuthenticationClick"
67+
>
68+
<UButton
69+
block
70+
icon="i-fa6-solid-lock"
71+
:loading="
72+
enableTwoFactorAuthenticationStatus === 'pending' || twoFactorQrCodeStatus === 'pending'
73+
"
74+
:disabled="twoFactorQrCodeData?.svg"
75+
@click="enableTwoFactorAuthenticationClick"
76+
>
77+
{{ $t('auth.enableTwoFactorAuthenticationForm.action.enable.label') }}
78+
</UButton>
79+
</AuthNeedsToConfirmUserPasswordButton>
80+
81+
<div
82+
v-if="twoFactorQrCodeData?.svg"
83+
class="flex w-full flex-col items-center justify-center space-y-4 overflow-hidden"
84+
>
85+
<!-- eslint-disable vue/no-v-html -->
86+
<div
87+
class="-mx-2 inline-block rounded-lg bg-white p-2 text-black"
88+
v-html="twoFactorQrCodeData?.svg.replace('#2d3748', 'currentColor')"
89+
/>
90+
<!-- eslint-enable vue/no-v-html -->
91+
92+
<div class="flex flex-col items-center gap-1 text-sm text-gray-500">
93+
<div class="text-wrap">
94+
{{ $t('auth.enableTwoFactorAuthenticationForm.qrCode.description') }}
95+
</div>
96+
<div class="inline-flex items-center gap-1 rounded-lg bg-gray-100 px-2 py-1 dark:bg-gray-950">
97+
<code>{{ twoFactorQrCodeSecret }}</code>
98+
<CopyButton
99+
v-if="twoFactorQrCodeSecret"
100+
:value="twoFactorQrCodeSecret"
101+
color="gray"
102+
class="p-0"
103+
/>
104+
</div>
105+
</div>
106+
107+
<FormKit
108+
v-slot="{ state: { valid } }"
109+
v-model="form"
110+
type="form"
111+
:actions="false"
112+
:disabled="confirmedTwoFactorAuthenticationStatus === 'success'"
113+
:classes="{
114+
form: 'w-full',
115+
}"
116+
@submit="submitConfirmedTwoFactorAuthentication"
117+
>
118+
<FormErrorsAlert v-if="errorMessages" :error-messages="errorMessages" />
119+
120+
<FormKit
121+
type="otp"
122+
name="code"
123+
:label="$t('auth.enableTwoFactorAuthenticationForm.form.confirm.label')"
124+
validation="required"
125+
/>
126+
127+
<UButton
128+
type="submit"
129+
block
130+
:disabled="!valid || !!Object.keys(errorMessages).length"
131+
:loading="confirmedTwoFactorAuthenticationStatus === 'pending'"
132+
icon="i-fa6-solid-floppy-disk"
133+
>
134+
{{ $t('global.action.save.label') }}
135+
</UButton>
136+
</FormKit>
137+
</div>
138+
</template>

0 commit comments

Comments
 (0)