Skip to content

Commit

Permalink
feat: Etusivu palvelulle ennen palvelun avaamista (#606)
Browse files Browse the repository at this point in the history
* Lisää uusi next-app väliaikaiselle etusivulle

* mahdollista tyypillisen layoutin käytöstä poistaminen sivuilla

* parantele sivulayoutin tyyliä, jos sisällölle tulee scrollbar

* käytä väliaikaisessa appissa _documenttia, joka on pääasiallisessa next-apissa ja muokkaa Headin titlejä HASSU:sta Valtion liikenneväylien suunnitteluksi

* lisää next.configiin assetPrefix, jotta tailwindcss tyylit löytyy

* muokkaa kuvien urleja

* ohjaa pyynnöt frontendilta /etusivu:lle jos basic auth ei ole kunnossa. Pyydä basic auth /yllapito/kirjaudu-polusta.

---------

Co-authored-by: Mikko Haapamäki <mikko.haapamaki@cgi.com>
  • Loading branch information
tkork and haapamakim authored Feb 16, 2023
1 parent 8ad485b commit a8dce19
Show file tree
Hide file tree
Showing 25 changed files with 354 additions and 51 deletions.
5 changes: 5 additions & 0 deletions backend/test/lambdaAtEdge/cfUtil.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export interface CFResponse {
readonly headers: Record<string, Record<string, { value: string }>[]>;
readonly status: number;
readonly statusText: string;
}
56 changes: 56 additions & 0 deletions backend/test/lambdaAtEdge/frontendRequest.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { describe } from "mocha";
import { CFResponse } from "./cfUtil";

const handler = require("../../../deployment/lib/lambda/frontendRequest").handler;

const { expect } = require("chai");
const authorizedFakeHeader = "Basic " + Buffer.from("${BASIC_USERNAME}:${BASIC_PASSWORD}").toString("base64");

function runTest(uri: string, authenticated: boolean, expectedStatus: number | undefined, expectedLocation?: string) {
handler(
{
Records: [
{
cf: {
request: {
headers: authenticated
? {
authorization: [{ value: authorizedFakeHeader }],
}
: {},
method: "GET",
uri,
},
},
},
],
},
null,
(_request: unknown, response: CFResponse) => {
const testData = "(uri:" + uri + ", " + (authenticated ? "authenticated" : "unauthenticated") + ")";
expect(response.status).to.eq(expectedStatus, "testData:" + testData);
if (expectedLocation !== undefined) {
expect(response.headers["location"]?.[0].value).to.eq(
expectedLocation,
"expected redirect location to be " + expectedLocation + " testData:" + testData
);
}
}
);
}

describe("frontendRequest lambda@edge", () => {
it("should return correct response for different paths", async () => {
runTest("/", false, 302, "/etusivu/index.html");
runTest("/something", false, 401);
runTest("/yllapito/kirjaudu", false, 401);

// Basic auth kunnossa
runTest("/something", true, undefined);
runTest("/yllapito/kirjaudu", true, undefined);

// Etusivu on auki kaikille
runTest("/etusivu", false, undefined);
runTest("/etusivu/index.html", false, undefined);
});
});
16 changes: 8 additions & 8 deletions backend/test/lambdaAtEdge/tiedostotOriginResponse.test.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
// @ts-nocheck
import { describe } from "mocha";
import { CFResponse } from "./cfUtil";

const handler = require("../../../deployment/lib/lambda/tiedostotOriginResponse").handler;

const { expect } = require("chai");

const createEvent = (publicationTimestamp: string | null, expirationTimestamp?: string | null) => {
const headers = {};
const headers: Record<string, unknown> = {};
if (publicationTimestamp) {
headers["x-amz-meta-publication-timestamp"] = [
{
Expand Down Expand Up @@ -42,7 +42,7 @@ const createEvent = (publicationTimestamp: string | null, expirationTimestamp?:

describe("Tiedostot publication time handler", () => {
it("should return 404 for unpublished files based on x-amz-meta-publication-timestamp", () => {
handler(createEvent("2222-03-09T08:31:00.000Z"), null, (request, response) => {
handler(createEvent("2222-03-09T08:31:00.000Z"), null, (request: unknown, response: CFResponse) => {
expect(response.status).to.eq("404");
expect(response.headers).to.eql({
Expires: [
Expand All @@ -56,21 +56,21 @@ describe("Tiedostot publication time handler", () => {
});

it("should return 200 for published files based on x-amz-meta-publication-timestamp", () => {
handler(createEvent("2022-03-09T08:31:00.000Z"), null, (request, response) => {
handler(createEvent("2022-03-09T08:31:00.000Z"), null, (request: unknown, response: CFResponse) => {
expect(response.status).to.eq("200");
expect(response.headers).to.eql({});
});
});

it("should return 404 for unpublished files based on x-amz-meta-expiration-timestamp", () => {
handler(createEvent(null, "2000-03-09T08:31:00.000Z"), null, (request, response) => {
handler(createEvent(null, "2000-03-09T08:31:00.000Z"), null, (request: unknown, response: CFResponse) => {
expect(response.status).to.eq("404");
expect(response.headers).to.be.undefined;
});
});

it("should return 200 for published files based on x-amz-meta-expiration-timestamp", () => {
handler(createEvent(null, "2222-03-09T08:31:00.000Z"), null, (request, response) => {
handler(createEvent(null, "2222-03-09T08:31:00.000Z"), null, (request: unknown, response: CFResponse) => {
expect(response.status).to.eq("200");
expect(response.headers).to.eql({
Expires: [
Expand All @@ -84,7 +84,7 @@ describe("Tiedostot publication time handler", () => {
});

it("should return 404 for unpublished files based on x-amz-meta-publication-timestamp and x-amz-meta-expiration-timestamp", () => {
handler(createEvent("2222-03-09T08:31:00.000Z", "2223-03-09T08:31:00.000Z"), null, (request, response) => {
handler(createEvent("2222-03-09T08:31:00.000Z", "2223-03-09T08:31:00.000Z"), null, (request: unknown, response: CFResponse) => {
expect(response.status).to.eq("404");
expect(response.headers).to.eql({
Expires: [
Expand All @@ -98,7 +98,7 @@ describe("Tiedostot publication time handler", () => {
});

it("should return 200 for published files based on x-amz-meta-publication-timestamp and x-amz-meta-expiration-timestamp", () => {
handler(createEvent("2022-03-09T08:31:00.000Z", "2222-03-09T08:31:00.000Z"), null, (request, response) => {
handler(createEvent("2022-03-09T08:31:00.000Z", "2222-03-09T08:31:00.000Z"), null, (request: unknown, response: CFResponse) => {
expect(response.status).to.eq("200");
expect(response.headers).to.eql({
Expires: [
Expand Down
1 change: 1 addition & 0 deletions deployment/lib/buildspec/buildspec-dev.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ phases:
- npm run test
- npm run localstack:stop &
- npm run sonar
- npm run export-under-construction
- npm run deploy:database
- npm run deploy:backend
- npm run deploy:frontend
Expand Down
1 change: 1 addition & 0 deletions deployment/lib/buildspec/buildspec-prod.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ phases:
build:
commands:
- npm run get-next-version
- npm run export-under-construction
- npm run maintenancemode set
- npm run deploy:database
- npm run deploy:backend
Expand Down
1 change: 1 addition & 0 deletions deployment/lib/buildspec/buildspec-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ phases:
- npm run setupenvironment
- npm run test
- npm run localstack:stop &
- npm run export-under-construction
- npm run deploy:database
- npm run deploy:backend
- npm run deploy:frontend
Expand Down
1 change: 1 addition & 0 deletions deployment/lib/buildspec/buildspec-training.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ phases:
- npm run setupenvironment
- npm run test
- npm run localstack:stop &
- npm run export-under-construction
- npm run maintenancemode set
- npm run deploy:database
- npm run deploy:backend
Expand Down
60 changes: 33 additions & 27 deletions deployment/lib/hassu-frontend.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,8 @@ export class HassuFrontendStack extends Stack {
config,
dmzProxyBehaviorWithLambda,
dmzProxyBehavior,
edgeFunctionRole
edgeFunctionRole,
frontendRequestFunction
);

let domain: any;
Expand Down Expand Up @@ -187,9 +188,7 @@ export class HassuFrontendStack extends Stack {
behaviours,
domain,
defaultBehavior: {
edgeLambdas: frontendRequestFunction
? [{ functionVersion: frontendRequestFunction.currentVersion, eventType: LambdaEdgeEventType.VIEWER_REQUEST }]
: [],
edgeLambdas: [{ functionVersion: frontendRequestFunction.currentVersion, eventType: LambdaEdgeEventType.VIEWER_REQUEST }],
},
cloudfrontProps: { priceClass: PriceClass.PRICE_CLASS_100, logBucket, webAclId },
invalidationPaths: ["/*"],
Expand Down Expand Up @@ -318,17 +317,19 @@ export class HassuFrontendStack extends Stack {
config: Config,
dmzProxyBehaviorWithLambda: BehaviorOptions,
dmzProxyBehavior: BehaviorOptions,
edgeFunctionRole: Role
edgeFunctionRole: Role,
frontendRequestFunction: EdgeFunction
): Promise<Record<string, BehaviorOptions>> {
const { keyGroups, originAccessIdentity, originAccessIdentityReportBucket } = await this.createTrustedKeyGroupsAndOAI(config);
const props: Record<string, any> = {
"/oauth2/*": dmzProxyBehaviorWithLambda,
"/graphql": dmzProxyBehaviorWithLambda,
"/tiedostot/*": await this.createPublicBucketBehavior(env, edgeFunctionRole, originAccessIdentity),
"/tiedostot/*": await this.createPublicBucketBehavior(env, edgeFunctionRole, frontendRequestFunction, originAccessIdentity),
"/yllapito/tiedostot/*": await this.createPrivateBucketBehavior(
"yllapitoBucket",
Config.yllapitoBucketName,
keyGroups,
frontendRequestFunction,
originAccessIdentity
),
"/yllapito/graphql": dmzProxyBehaviorWithLambda,
Expand All @@ -340,6 +341,7 @@ export class HassuFrontendStack extends Stack {
"reportBucket",
Config.reportBucketName,
keyGroups,
frontendRequestFunction,
originAccessIdentityReportBucket
);
}
Expand All @@ -364,54 +366,58 @@ export class HassuFrontendStack extends Stack {
return dmzBehavior;
}

private async createPrivateBucketBehavior(
name: string,
bucketName: string,
keyGroups: KeyGroup[],
private async createPublicBucketBehavior(
env: string,
role: Role,
frontendRequestFunction: EdgeFunction,
originAccessIdentity?: IOriginAccessIdentity
): Promise<BehaviorOptions> {
const tiedostotOriginResponseFunction = this.createTiedostotOriginResponseFunction(env, role);
return {
origin: new S3Origin(
Bucket.fromBucketAttributes(this, name + "Origin", {
Bucket.fromBucketAttributes(this, "tiedostotBucketOrigin", {
region: "eu-west-1",
bucketName,
bucketName: Config.publicBucketName,
}),
{
originAccessIdentity,
}
),
compress: true,
cachePolicy: CachePolicy.CACHING_DISABLED,
cachePolicy: CachePolicy.CACHING_OPTIMIZED,
originRequestPolicy: OriginRequestPolicy.CORS_S3_ORIGIN,
trustedKeyGroups: keyGroups,
edgeLambdas: [
{ functionVersion: frontendRequestFunction.currentVersion, eventType: LambdaEdgeEventType.VIEWER_REQUEST },
{
functionVersion: tiedostotOriginResponseFunction.currentVersion,
eventType: LambdaEdgeEventType.ORIGIN_RESPONSE,
},
],
};
}

private async createPublicBucketBehavior(
env: string,
role: Role,
private async createPrivateBucketBehavior(
name: string,
bucketName: string,
keyGroups: KeyGroup[],
frontendRequestFunction: EdgeFunction,
originAccessIdentity?: IOriginAccessIdentity
): Promise<BehaviorOptions> {
const tiedostotOriginResponseFunction = this.createTiedostotOriginResponseFunction(env, role);
return {
origin: new S3Origin(
Bucket.fromBucketAttributes(this, "tiedostotBucketOrigin", {
Bucket.fromBucketAttributes(this, name + "Origin", {
region: "eu-west-1",
bucketName: Config.publicBucketName,
bucketName,
}),
{
originAccessIdentity,
}
),
compress: true,
cachePolicy: CachePolicy.CACHING_OPTIMIZED,
cachePolicy: CachePolicy.CACHING_DISABLED,
originRequestPolicy: OriginRequestPolicy.CORS_S3_ORIGIN,
edgeLambdas: [
{
functionVersion: tiedostotOriginResponseFunction.currentVersion,
eventType: LambdaEdgeEventType.ORIGIN_RESPONSE,
},
],
trustedKeyGroups: keyGroups,
edgeLambdas: [{ functionVersion: frontendRequestFunction.currentVersion, eventType: LambdaEdgeEventType.VIEWER_REQUEST }],
};
}

Expand Down
50 changes: 38 additions & 12 deletions deployment/lib/lambda/frontendRequest.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,18 +16,44 @@ exports.handler = (event, context, callback) => {
// Continue request processing if authentication passed
callback(null, request);
} else {
callback(null, {
status: 401,
statusDescription: "Unauthorized",
headers: {
"www-authenticate": [
{
key: "www-authenticate",
value: "Basic",
},
],
},
});
if (request.uri === "/" || request.uri === "") {
// Ohjaa juuresta /etusivu:lle
callback(null, {
status: 302,
statusDescription: "Found",
headers: {
location: [
{
key: "Location",
value: "/etusivu/index.html",
},
],
},
});
} else if (
request.uri.startsWith("/etusivu") ||
request.uri.startsWith("/oauth2") ||
request.uri.startsWith("/fonts") ||
request.uri === "/favicon.ico"
) {
// Etusivu ennen palvelun avaamista yleisölle on suojaamaton.
// /oauth2 pitää olla avoin, jotta kirjautuminen toimii
callback(null, request);
} else {
// Pyydä kaikkialla muualla Basic Authentication-tunnukset
callback(null, {
status: 401,
statusDescription: "Unauthorized",
headers: {
"www-authenticate": [
{
key: "www-authenticate",
value: "Basic",
},
],
},
});
}
}
};

Expand Down
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,8 @@
"dev": "VERSION=`git rev-parse --abbrev-ref HEAD` next dev",
"build": "next build",
"start": "next start",
"export-under-construction": "next build ./under-construction && next export -o public/etusivu ./under-construction",
"dev-under-construction": "next dev ./under-construction",
"cypress": "cypress",
"generate:velhoapi": "MSYS_NO_PATHCONV=1 cross-env docker run --rm --env-file .env.test -e VELHO_AUTH_URL -e VELHO_API_URL -e VELHO_USERNAME -e VELHO_PASSWORD -e VELHO_ACCESS_TOKEN=\"$(ts-node --project=tsconfig.cdk.json -r dotenv/config ./tools/velho/authenticate.ts dotenv_config_path=.env.test)\" -v $INIT_CWD:/work ${ACCOUNT_ID:=283563576583}.dkr.ecr.eu-west-1.amazonaws.com/hassu-build-image:$(cat .buildimageversion) /work/tools/velho/gradlew -p /work/tools/velho --info",
"postgenerate:velhoapi": "ts-node --project=tsconfig.cdk.json ./tools/velho/processMetaData.ts ./backend/src/velho/metadata.json ./common/generated/velhometadata.json ./common/generated/ely.json",
Expand Down
1 change: 1 addition & 0 deletions public/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
etusivu
Binary file added public/rata_ja_tie_background.jpeg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
14 changes: 14 additions & 0 deletions src/components/layout/ConditionalWrapper.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import React, { FC, ReactNode, ComponentProps } from "react";

type ConditionalWrapperProps<T extends FC> = {
condition: boolean;
wrapper: T;
children?: ReactNode;
wrapperProps?: Omit<ComponentProps<T>, "children">;
};

function ConditionalWrapper<T extends FC>({ condition, wrapper, children, wrapperProps }: ConditionalWrapperProps<T>) {
return <>{condition ? wrapper({ children, ...wrapperProps }) : children}</>;
}

export default ConditionalWrapper;
Loading

0 comments on commit a8dce19

Please sign in to comment.