Skip to content

Commit

Permalink
N21-2133 Authentication (#10)
Browse files Browse the repository at this point in the history
  • Loading branch information
MarvinOehlerkingCap authored Aug 26, 2024
1 parent 9386741 commit 07cacf5
Show file tree
Hide file tree
Showing 50 changed files with 1,567 additions and 530 deletions.
2 changes: 1 addition & 1 deletion ansible/group_vars/develop/shd-client.yml
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
---
SHD_CLIENT_IMAGE: ghcr.io/hpi-schul-cloud/shd-client
SHD_CLIENT_PREFIX: shd2-
SHD_CLIENT_PREFIX: superhero-
2 changes: 1 addition & 1 deletion ansible/roles/shd-client-core/defaults/main.yml
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
---
SHD_CLIENT_IMAGE: quay.io/schulcloudverbund/shd-client
SHD_CLIENT_PREFIX: dashboard2.
SHD_CLIENT_PREFIX: superhero.
6 changes: 0 additions & 6 deletions ansible/roles/shd-client-core/templates/ingress.yml.j2
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,6 @@ metadata:
namespace: {{ NAMESPACE }}
annotations:
nginx.ingress.kubernetes.io/ssl-redirect: "{{ TLS_ENABLED|default("false") }}"
# type of authentication
nginx.ingress.kubernetes.io/auth-type: basic
# name of the secret that contains the user/password definitions
nginx.ingress.kubernetes.io/auth-secret: shd-basic-auth-secret
# message to display with an appropriate context why the authentication is required
nginx.ingress.kubernetes.io/auth-realm: 'Authentication Required'
{% if CLUSTER_ISSUER is defined %}
cert-manager.io/cluster-issuer: {{ CLUSTER_ISSUER }}
{% endif %}
Expand Down
1 change: 1 addition & 0 deletions config/webpack/webpack.dev.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ module.exports = merge(common, {
process.env.NODE_ENV === "development"
? {
port: 4100,
historyApiFallback: true,
allowedHosts: "all",
client: {
overlay: false,
Expand Down
325 changes: 137 additions & 188 deletions package-lock.json

Large diffs are not rendered by default.

8 changes: 4 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,9 @@
"jest"
],
"scripts": {
"serve": "NODE_ENV=development webpack serve --config config/webpack/webpack.dev.js",
"serve:windows": "set NODE_ENV=development&& webpack serve --config config/webpack/webpack.dev.js",
"build": "NODE_ENV=production webpack --config config/webpack/webpack.prod.js",
"test": "npm run test:unit",
"serve": "cross-env NODE_ENV=development webpack serve --config config/webpack/webpack.dev.js",
"build": "cross-env NODE_ENV=production webpack --config config/webpack/webpack.prod.js",
"test": "cross-env NODE_ENV=test npm run test:unit",
"test:unit": "npx jest",
"test:unit:ci": "npm run test:unit -- --coverage --ci --maxWorkers=4",
"lint": "npx eslint 'src/**/*.{ts,js,vue}'",
Expand All @@ -19,6 +18,7 @@
"dependencies": {
"@vueuse/core": "^10.11.0",
"axios": "^1.7.2",
"cross-env": "^7.0.3",
"dayjs": "^1.11.12",
"pinia": "^2.2.0",
"ts-node": "^10.9.2",
Expand Down
14 changes: 10 additions & 4 deletions src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -8,22 +8,28 @@
</div>
<h1 class="ma-0">New Superhero-Dashboard</h1>
</div>

<LogoutBtn />

<nav>
<VTabs align-tabs="center">
<VTab to="/">Home</VTab>
<VTab to="/about">About</VTab>
<VTab to="/login">Login</VTab>
</VTabs>
<VDivider />
</nav>
</div>
</header>

<div class="d-flex justify-center">
<RouterView />
</div>
<main>
<div class="d-flex justify-center">
<RouterView />
</div>
</main>
</VApp>
</template>

<script setup lang="ts">
import { LogoutBtn } from "@feature/logout";
import { RouterView } from "vue-router";
</script>
7 changes: 6 additions & 1 deletion src/locales/de.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
export default {
"common.actions.login": "Anmelden",
"common.actions.logout": "Abmelden",
"common.words.emailAddress": "E-Mail Adresse",
"common.words.password": "Passwort",
"error.generic": "Ein Fehler ist aufgetreten",
"error.load": "Fehler beim Laden der Daten.",
"error.load": "Fehler beim Laden der Daten",
"error.login.noSuperhero": "Der ausgewählte Account ist kein Superhero",
};
7 changes: 6 additions & 1 deletion src/locales/en.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
export default {
"common.actions.login": "Log in",
"common.actions.logout": "Log out",
"common.words.emailAddress": "E-Mail Address",
"common.words.password": "Password",
"error.generic": "An error has occurred",
"error.load": "Error while loading the data.",
"error.load": "Error while loading the data",
"error.login.noSuperhero": "The selected account is not a superhero",
};
7 changes: 6 additions & 1 deletion src/locales/es.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
export default {
"common.actions.login": "Conectarse",
"common.actions.logout": "Cerrar sesión",
"common.words.emailAddress": "Correo electrónico",
"common.words.password": "Contraseña",
"error.generic": "Se ha producido un error",
"error.load": "Error al cargar los datos.",
"error.load": "Error al cargar los datos",
"error.login.noSuperhero": "La cuenta seleccionada no es un superhéroe",
};
7 changes: 6 additions & 1 deletion src/locales/uk.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
export default {
"common.actions.login": "зареєструватися",
"common.actions.logout": "Вийти",
"common.words.emailAddress": "адреса електронної пошти",
"common.words.password": "пароль",
"error.generic": "Виникла помилка",
"error.load": "Помилка під час завантаження даних.",
"error.load": "Помилка під час завантаження даних",
"error.login.noSuperhero": "Вибраний обліковий запис не є супергероєм",
};
11 changes: 6 additions & 5 deletions src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,11 @@ import { createI18n } from "@/plugins/i18n";
import vuetify from "@/plugins/vuetify";
import router from "@/router";
import { initializeAxios } from "@/utils/api";
import { useAuthStore } from "@data/auth";
import { useAuthStore, useJwtCookie } from "@data/auth";
import { useEnvConfigStore } from "@data/env-config";
import { htmlConfig } from "@feature/render-html";
import axios from "axios";
import { createPinia } from "pinia";
import Cookies from "universal-cookie";
import { createApp } from "vue";
import VueDOMPurifyHTML from "vue-dompurify-html";
import App from "./App.vue";
Expand Down Expand Up @@ -38,13 +37,15 @@ app.use(VueDOMPurifyHTML, {

await useEnvConfigStore().loadConfig();

const cookies = new Cookies();
const jwt = cookies.get("jwt");
const authStore = useAuthStore();
const { getJwt } = useJwtCookie();

const jwt = getJwt();
if (jwt) {
axios.defaults.headers.common["Authorization"] = "Bearer " + jwt;

try {
await useAuthStore().login();
await authStore.fetchMe();
} catch (e) {
// eslint-disable-next-line no-console
console.error("### JWT invalid: ", e);
Expand Down
30 changes: 30 additions & 0 deletions src/modules/data/application-error/applicationError.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
import {
ApiResponseError,
ApiValidationError,
mapAxiosErrorToResponseError,
} from "@/utils/api";
import { HttpStatusCode } from "./httpStatusCode.enum";

export interface ApplicationErrorProps {
Expand All @@ -18,4 +23,29 @@ export class ApplicationError extends Error {
this.statusCode = props.statusCode;
this.translationKey = props.translationKey;
}

static fromUnknown(
error: unknown,
translationKey?: string
): ApplicationError {
if (error instanceof ApplicationError) {
return error;
}

const apiError: ApiResponseError | ApiValidationError =
mapAxiosErrorToResponseError(error);

return ApplicationError.fromApiError(apiError, translationKey);
}

static fromApiError(
apiError: ApiResponseError | ApiValidationError,
translationKey = "error.generic"
): ApplicationError {
return new ApplicationError({
statusCode: apiError.code,
message: `${apiError.title}: ${apiError.message}`,
translationKey,
});
}
}
126 changes: 126 additions & 0 deletions src/modules/data/application-error/applicationError.unit.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
import { defaultApiError, mapAxiosErrorToResponseError } from "@/utils/api";
import {
apiResponseErrorFactory,
applicationErrorFactory,
axiosErrorFactory,
} from "@@/tests/test-utils/factory";
import { ApplicationError } from "./applicationError";

describe(ApplicationError.name, () => {
describe("fromUnknown", () => {
describe("when the error is already an application error", () => {
const setup = () => {
const error = applicationErrorFactory.build();

return {
error,
};
};

it("should return the application error", () => {
const { error } = setup();

const result = ApplicationError.fromUnknown(error);

expect(result).toEqual<ApplicationError>(error);
});
});

describe("when the error is an axios error", () => {
const setup = () => {
const error = axiosErrorFactory.build();

return {
error,
};
};

it("should return an application error from the api error", () => {
const { error } = setup();

const result = ApplicationError.fromUnknown(error);

expect(result).toEqual<ApplicationError>(
ApplicationError.fromApiError(mapAxiosErrorToResponseError(error))
);
});
});

describe("when the error is not a known type", () => {
const setup = () => {
const error = new Error("test");

return {
error,
};
};

it("should return a generic application error from the default api error", () => {
const { error } = setup();

const result = ApplicationError.fromUnknown(error);

expect(result).toEqual<ApplicationError>(
new ApplicationError({
statusCode: defaultApiError.code,
message: `${defaultApiError.title}: ${defaultApiError.message}`,
translationKey: "error.generic",
})
);
});
});
});

describe("fromApiError", () => {
describe("when using the default translation key", () => {
const setup = () => {
const apiError = apiResponseErrorFactory.build();

return {
apiError,
};
};

it("should return an application error with a generic message", () => {
const { apiError } = setup();

const result = ApplicationError.fromApiError(apiError);

expect(result).toEqual<ApplicationError>(
new ApplicationError({
statusCode: apiError.code,
message: `${apiError.title}: ${apiError.message}`,
translationKey: "error.generic",
})
);
});
});

describe("when specifying a translation key", () => {
const setup = () => {
const apiError = apiResponseErrorFactory.build();

return {
apiError,
};
};

it("should return an application error with the translation key", () => {
const { apiError } = setup();

const result = ApplicationError.fromApiError(
apiError,
"error.not.generic"
);

expect(result).toEqual<ApplicationError>(
new ApplicationError({
statusCode: apiError.code,
message: `${apiError.title}: ${apiError.message}`,
translationKey: "error.not.generic",
})
);
});
});
});
});
Loading

0 comments on commit 07cacf5

Please sign in to comment.