small works
Some checks failed
CI / Lint (push) Failing after 7m46s
CI / Test (push) Failing after 8m24s

This commit is contained in:
2025-04-04 18:21:37 +02:00
parent 211393728d
commit 0b29bc2d44
7 changed files with 297 additions and 31 deletions

View File

@@ -5,14 +5,21 @@ import { inject as service } from '@ember/service';
export default class ApplicationAdapter extends JSONAPIAdapter { export default class ApplicationAdapter extends JSONAPIAdapter {
@service session; @service session;
namespace = 'api/v1'; get namespace() {
if (this.session.isAuthenticated) {
return 'api/v1';
} else {
return 'api/v1/public';
}
}
@computed('session.{data.authenticated.access_token,isAuthenticated}') @computed('session.{data.authenticated.access_token,isAuthenticated}')
get headers() { get headers() {
let headers = {}; let headers = {};
if (this.session.isAuthenticated) { if (this.session.isAuthenticated) {
// OAuth 2 // OAuth 2
headers['Authorization'] = `Bearer ${this.session.data.authenticated.access_token}`; headers['Authorization'] =
`Bearer ${this.session.data.authenticated.access_token}`;
} }
return headers; return headers;

View File

@@ -82,7 +82,7 @@ export default BaseAuthenticator.extend({
*/ */
restore(data) { restore(data) {
if (this._refreshTokenTimeout) { if (this._refreshTokenTimeout) {
run.cancel(this._refreshTokenTimeout); cancel(this._refreshTokenTimeout);
delete this._refreshTokenTimeout; delete this._refreshTokenTimeout;
} }
return this._refreshAccessToken(data); return this._refreshAccessToken(data);
@@ -106,13 +106,14 @@ export default BaseAuthenticator.extend({
*/ */
authenticate(identification, password) { authenticate(identification, password) {
return new RSVP.Promise((resolve, reject) => { return new RSVP.Promise((resolve, reject) => {
const { const { identificationAttributeName, passwordAttributeName } =
identificationAttributeName, this.getProperties(
passwordAttributeName 'identificationAttributeName',
} = this.getProperties('identificationAttributeName', 'passwordAttributeName'); 'passwordAttributeName',
);
const data = { const data = {
[identificationAttributeName]: identification, [identificationAttributeName]: identification,
[passwordAttributeName]: password [passwordAttributeName]: password,
}; };
const serverTokenEndpoint = this.get('serverTokenEndpoint'); const serverTokenEndpoint = this.get('serverTokenEndpoint');
@@ -141,7 +142,7 @@ export default BaseAuthenticator.extend({
@public @public
*/ */
invalidate() { invalidate() {
run.cancel(this._refreshTokenTimeout); cancel(this._refreshTokenTimeout);
delete this._refreshTokenTimeout; delete this._refreshTokenTimeout;
return RSVP.Promise.resolve(); return RSVP.Promise.resolve();
}, },
@@ -161,22 +162,24 @@ export default BaseAuthenticator.extend({
const options = { const options = {
body: JSON.stringify(data), body: JSON.stringify(data),
headers, headers,
method: 'POST' method: 'POST',
}; };
return new RSVP.Promise((resolve, reject) => { return new RSVP.Promise((resolve, reject) => {
fetch(url, options).then((response) => { fetch(url, options)
response.text().then((text) => { .then((response) => {
let json = text ? JSON.parse(text) : {}; response.text().then((text) => {
if (!response.ok) { let json = text ? JSON.parse(text) : {};
response.responseJSON = json; if (!response.ok) {
reject(response); response.responseJSON = json;
} else { reject(response);
window.localStorage.setItem('jwtLastRefreshAt', Date.now()); } else {
resolve(json); window.localStorage.setItem('jwtLastRefreshAt', Date.now());
} resolve(json);
}); }
}).catch(reject); });
})
.catch(reject);
}); });
}, },
@@ -219,7 +222,7 @@ export default BaseAuthenticator.extend({
const offset = 1000; // Refresh 1 sec before JWT expires const offset = 1000; // Refresh 1 sec before JWT expires
const now = Date.now(); const now = Date.now();
const waitMs = (jwtPayloadExpiresAt * 1000) - now - offset; //expiresAt is in sec const waitMs = jwtPayloadExpiresAt * 1000 - now - offset; //expiresAt is in sec
if (this._refreshTokenTimeout) { if (this._refreshTokenTimeout) {
cancel(this._refreshTokenTimeout); cancel(this._refreshTokenTimeout);
@@ -228,12 +231,18 @@ export default BaseAuthenticator.extend({
// Reschedule if the JWT is still valid // Reschedule if the JWT is still valid
if (waitMs > 0) { if (waitMs > 0) {
this._refreshTokenTimeout = later(this, this._refreshAccessToken, data, waitMs); this._refreshTokenTimeout = later(
this,
this._refreshAccessToken,
data,
waitMs,
);
} }
}, },
_refreshAccessToken(data) { _refreshAccessToken(data) {
var timeElapsedSinceLastRefresh = Date.now() - window.localStorage.getItem('jwtLastRefreshAt') var timeElapsedSinceLastRefresh =
Date.now() - window.localStorage.getItem('jwtLastRefreshAt');
if (timeElapsedSinceLastRefresh <= this.get('refreshTokenAfter')) { if (timeElapsedSinceLastRefresh <= this.get('refreshTokenAfter')) {
// Request attempted too soon! Reschedule // Request attempted too soon! Reschedule
return this._validateTokenAndScheduleRefresh(data); return this._validateTokenAndScheduleRefresh(data);
@@ -242,7 +251,9 @@ export default BaseAuthenticator.extend({
const serverRefreshTokenEndpoint = this.get('serverRefreshTokenEndpoint'); const serverRefreshTokenEndpoint = this.get('serverRefreshTokenEndpoint');
return new RSVP.Promise((resolve, reject) => { return new RSVP.Promise((resolve, reject) => {
this.makeRequest(serverRefreshTokenEndpoint, data, { Authorization:data["access_token"] }) this.makeRequest(serverRefreshTokenEndpoint, data, {
Authorization: data['access_token'],
})
.then((response) => { .then((response) => {
return this._validateTokenAndScheduleRefresh(response); return this._validateTokenAndScheduleRefresh(response);
}) })
@@ -257,7 +268,7 @@ export default BaseAuthenticator.extend({
reason = JSON.stringify(reason.responseJSON); reason = JSON.stringify(reason.responseJSON);
} }
warn(`JWT token could not be refreshed: ${reason}.`, false, { warn(`JWT token could not be refreshed: ${reason}.`, false, {
id: 'ember-simple-auth-jwt.failedJWTTokenRefresh' id: 'ember-simple-auth-jwt.failedJWTTokenRefresh',
}); });
reject(); reject();
@@ -267,10 +278,12 @@ export default BaseAuthenticator.extend({
_validateTokenAndScheduleRefresh(response) { _validateTokenAndScheduleRefresh(response) {
if (!this._validate(response)) { if (!this._validate(response)) {
return RSVP.Promise.reject('token is missing or invalid in server response'); return RSVP.Promise.reject(
'token is missing or invalid in server response',
);
} }
this._scheduleTokenRefresh(response); this._scheduleTokenRefresh(response);
return RSVP.Promise.resolve(response); return RSVP.Promise.resolve(response);
} },
}); });

View File

@@ -0,0 +1,2 @@
<!-- Current: "bg-gray-900 text-white", Default: "text-gray-300 hover:bg-gray-700 hover:text-white" -->
<a href={{@href}} class="rounded-md px-3 py-2 text-sm font-medium text-gray-300 hover:bg-gray-700 hover:text-white" aria-current="page">{{@title}}</a>

View File

@@ -0,0 +1,218 @@
<nav class="bg-gray-800">
<div class="mx-auto max-w-7xl sm:px-6 lg:px-8">
<div class="border-b border-gray-700">
<div class="flex h-16 items-center justify-between px-4 sm:px-0">
<div class="flex items-center">
<div class="shrink-0">
<img
class="size-8"
src="https://tailwindui.com/plus-assets/img/logos/mark.svg?color=indigo&shade=500"
alt="Your Company"
/>
</div>
<div class="hidden md:block">
<div class="ml-10 flex items-baseline space-x-4">
<Topbar::Item @title="Dashboard" @href="#" />
<Topbar::Item @title="Team" @href="#" />
<Topbar::Item @title="Projects" @href="#" />
<Topbar::Item @title="Calendar" @href="#" />
<Topbar::Item @title="Reports" @href="#" />
</div>
</div>
</div>
<div class="hidden md:block">
<div class="ml-4 flex items-center md:ml-6">
<button
type="button"
class="relative rounded-full bg-gray-800 p-1 text-gray-400 hover:text-white focus:outline-none focus:ring-2 focus:ring-white focus:ring-offset-2 focus:ring-offset-gray-800"
>
<span class="absolute -inset-1.5"></span>
<span class="sr-only">View notifications</span>
<svg
class="size-6"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
aria-hidden="true"
data-slot="icon"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M14.857 17.082a23.848 23.848 0 0 0 5.454-1.31A8.967 8.967 0 0 1 18 9.75V9A6 6 0 0 0 6 9v.75a8.967 8.967 0 0 1-2.312 6.022c1.733.64 3.56 1.085 5.455 1.31m5.714 0a24.255 24.255 0 0 1-5.714 0m5.714 0a3 3 0 1 1-5.714 0"
/>
</svg>
</button>
<!-- Profile dropdown -->
<div class="relative ml-3">
<div>
<button
type="button"
class="relative flex max-w-xs items-center rounded-full bg-gray-800 text-sm focus:outline-none focus:ring-2 focus:ring-white focus:ring-offset-2 focus:ring-offset-gray-800"
id="user-menu-button"
aria-expanded="false"
aria-haspopup="true"
>
<span class="absolute -inset-1.5"></span>
<span class="sr-only">Open user menu</span>
<img
class="size-8 rounded-full"
src="https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80"
alt=""
/>
</button>
</div>
<!--
Dropdown menu, show/hide based on menu state.
Entering: "transition ease-out duration-100"
From: "transform opacity-0 scale-95"
To: "transform opacity-100 scale-100"
Leaving: "transition ease-in duration-75"
From: "transform opacity-100 scale-100"
To: "transform opacity-0 scale-95"
-->
<div
class="absolute right-0 z-10 mt-2 w-48 origin-top-right rounded-md bg-white py-1 shadow-lg ring-1 ring-black/5 focus:outline-none"
role="menu"
aria-orientation="vertical"
aria-labelledby="user-menu-button"
tabindex="-1"
>
<Topbar::ProfileItem @title="Your Profile" />
<Topbar::ProfileItem @title="Settings" />
</div>
</div>
</div>
</div>
<div class="-mr-2 flex md:hidden">
<!-- Mobile menu button -->
<button
type="button"
class="relative inline-flex items-center justify-center rounded-md bg-gray-800 p-2 text-gray-400 hover:bg-gray-700 hover:text-white focus:outline-none focus:ring-2 focus:ring-white focus:ring-offset-2 focus:ring-offset-gray-800"
aria-controls="mobile-menu"
aria-expanded="false"
>
<span class="absolute -inset-0.5"></span>
<span class="sr-only">Open main menu</span>
<!-- Menu open: "hidden", Menu closed: "block" -->
<svg
class="block size-6"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
aria-hidden="true"
data-slot="icon"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25h16.5"
/>
</svg>
<!-- Menu open: "block", Menu closed: "hidden" -->
<svg
class="hidden size-6"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
aria-hidden="true"
data-slot="icon"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M6 18 18 6M6 6l12 12"
/>
</svg>
</button>
</div>
</div>
</div>
</div>
<!-- Mobile menu, show/hide based on menu state. -->
<div class="border-b border-gray-700 md:hidden" id="mobile-menu">
<div class="space-y-1 px-2 py-3 sm:px-3">
<!-- Current: "bg-gray-900 text-white", Default: "text-gray-300 hover:bg-gray-700 hover:text-white" -->
<a
href="#"
class="block rounded-md bg-gray-900 px-3 py-2 text-base font-medium text-white"
aria-current="page"
>Dashboard</a>
<a
href="#"
class="block rounded-md px-3 py-2 text-base font-medium text-gray-300 hover:bg-gray-700 hover:text-white"
>Team</a>
<a
href="#"
class="block rounded-md px-3 py-2 text-base font-medium text-gray-300 hover:bg-gray-700 hover:text-white"
>Projects</a>
<a
href="#"
class="block rounded-md px-3 py-2 text-base font-medium text-gray-300 hover:bg-gray-700 hover:text-white"
>Calendar</a>
<a
href="#"
class="block rounded-md px-3 py-2 text-base font-medium text-gray-300 hover:bg-gray-700 hover:text-white"
>Reports</a>
</div>
<div class="border-t border-gray-700 pb-3 pt-4">
<div class="flex items-center px-5">
<div class="shrink-0">
<img
class="size-10 rounded-full"
src="https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80"
alt=""
/>
</div>
<div class="ml-3">
<div class="text-base/5 font-medium text-white">Tom Cook</div>
<div class="text-sm font-medium text-gray-400">tom@example.com</div>
</div>
<button
type="button"
class="relative ml-auto shrink-0 rounded-full bg-gray-800 p-1 text-gray-400 hover:text-white focus:outline-none focus:ring-2 focus:ring-white focus:ring-offset-2 focus:ring-offset-gray-800"
>
<span class="absolute -inset-1.5"></span>
<span class="sr-only">View notifications</span>
<svg
class="size-6"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
aria-hidden="true"
data-slot="icon"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M14.857 17.082a23.848 23.848 0 0 0 5.454-1.31A8.967 8.967 0 0 1 18 9.75V9A6 6 0 0 0 6 9v.75a8.967 8.967 0 0 1-2.312 6.022c1.733.64 3.56 1.085 5.455 1.31m5.714 0a24.255 24.255 0 0 1-5.714 0m5.714 0a3 3 0 1 1-5.714 0"
/>
</svg>
</button>
</div>
<div class="mt-3 space-y-1 px-2">
<a
href="#"
class="block rounded-md px-3 py-2 text-base font-medium text-gray-400 hover:bg-gray-700 hover:text-white"
>Your Profile</a>
<a
href="#"
class="block rounded-md px-3 py-2 text-base font-medium text-gray-400 hover:bg-gray-700 hover:text-white"
>Settings</a>
<a
href="#"
class="block rounded-md px-3 py-2 text-base font-medium text-gray-400 hover:bg-gray-700 hover:text-white"
>Sign out</a>
</div>
</div>
</div>
</nav>

View File

@@ -0,0 +1,8 @@
<!-- Active: "bg-gray-100 outline-none", Not Active: "" -->
<a
href="#"
class="block px-4 py-2 text-sm text-gray-700"
role="menuitem"
tabindex="-1"
id="user-menu-item-0"
>{{@title}}</a>

View File

@@ -5,6 +5,7 @@
<title>Holygamesapp</title> <title>Holygamesapp</title>
<meta name="description" content=""> <meta name="description" content="">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<script src="https://unpkg.com/@tailwindcss/browser@4"></script>
{{content-for "head"}} {{content-for "head"}}

View File

@@ -7,6 +7,23 @@
<LinkTo @route="login">Login</LinkTo> <LinkTo @route="login">Login</LinkTo>
{{/if}} {{/if}}
</div> </div>
<div class="main">
{{outlet}} <div class="min-h-full">
<div class="bg-gray-800 pb-32">
<Topbar::Navbar />
<header class="py-10">
<div class="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
<h1 class="text-3xl font-bold tracking-tight text-white">Dashboard</h1>
</div>
</header>
</div>
<main class="-mt-32">
<div class="mx-auto max-w-7xl px-4 pb-12 sm:px-6 lg:px-8">
<div class="rounded-lg bg-white px-5 py-6 shadow sm:px-6">
{{outlet}}
</div>
</div>
</main>
</div> </div>