Secures the i18n-manager apis. Fixes style bugs in the theme switcher

parent 64355228
......@@ -7,14 +7,15 @@ import * as bodyParser from 'body-parser';
import * as child_process from 'child_process';
import * as compress from 'compression';
import * as cors from 'cors';
import * as express from 'express';
import * as fs from 'fs';
import * as https from 'https';
import * as path from 'path';
import 'reflect-metadata';
// These are important and needed before anything else
import 'zone.js/dist/zone-node';
import { DefaultSettings } from './src/lib/default.settings';
// Faster server renders w/ Prod mode (dev mode never needed)
enableProdMode();
......@@ -42,12 +43,19 @@ const i18nFileBaseLocation = {
};
app.use(bodyParser.json({ limit: '50mb' }));
app.use(bodyParser.urlencoded({ limit: '50mb', extended: true }));
app.use(bodyParser.urlencoded({
limit: '50mb',
extended: true,
}));
app.use(cors(corsOptions));
app.use(compress());
app.param('project', (req, res, next, project) => {
if (!project || !i18nFileBaseLocation[project]) {
res.status(500).send({ status: 'STATUS:FAILED', data: 'Invalid Project specified', payload: { project } });
res.status(500).send({
status: 'STATUS:FAILED',
data: 'Invalid Project specified',
payload: { project },
});
} else {
req.i18nFileBaseLocation = i18nFileBaseLocation[project];
req.projectBaseLocation = projectBaseLocation[project];
......@@ -121,13 +129,21 @@ const buildKeys = ({ root, dataNode, langRef, langData }) => {
} else {
const value = {};
value[langRef] = dataNode;
langData.push({ key: root, value });
langData.push({
key: root,
value,
});
}
} else {
Object.keys(dataNode).forEach(key => {
const rootKey = root ? `${root}.` : '';
buildKeys({ root: `${rootKey}${key}`, dataNode: dataNode[key], langRef, langData });
buildKeys({
root: `${rootKey}${key}`,
dataNode: dataNode[key],
langRef,
langData,
});
});
}
};
......@@ -194,7 +210,8 @@ const getBranch = (req) => {
};
app.engine('html', ngExpressEngine({
bootstrap: RootServerModuleNgFactory, providers: [
bootstrap: RootServerModuleNgFactory,
providers: [
provideModuleMap(LAZY_MODULE_MAP),
],
}));
......@@ -204,13 +221,20 @@ app.set('views', path.join(DIST_FOLDER, 'browser'));
// TODO: implement data requests securely
app.get('/api/v1/plugin/i18nator/:project/langFile', async (req, res) => {
const payload = { langData: {}, unused: {}, branch: {} };
const payload = {
langData: {},
unused: {},
branch: {},
};
if (!cache[req.projectCache].langData) {
const langData = [];
availableLangs.forEach((langRef, index) => {
buildKeys({
root: '', dataNode: JSON.parse(fs.readFileSync(path.join(req.i18nFileBaseLocation, `${langRef}.json`)).toString('UTF-8')), langRef, langData,
root: '',
dataNode: JSON.parse(fs.readFileSync(path.join(req.i18nFileBaseLocation, `${langRef}.json`)).toString('UTF-8')),
langRef,
langData,
});
});
cache[req.projectCache].langData = langData;
......@@ -227,32 +251,90 @@ app.get('/api/v1/plugin/i18nator/:project/langFile', async (req, res) => {
}
payload.branch = cache[req.projectCache].branch;
res.send({ status: 'STATUS:SUCCESSFUL', payload });
res.send({
status: 'STATUS:SUCCESSFUL',
payload,
});
});
app.post('/api/v1/plugin/i18nator/:project/updateLang', async (req, res) => {
const username = req.body.username;
const token = req.body.token;
if (!username || !token) {
res.send({
status: 'STATUS:FAILED',
step: 'AUTHENTICATE_STATIC',
payload: { reason: 'UNKOWN_LOGIN' },
});
return;
}
if (!req.body.data) {
res.status(500).send({ status: 'STATUS:FAILED', data: 'Invalid Data', payload: { body: req.body } });
res.status(500).send({
status: 'STATUS:FAILED',
data: 'Invalid Data',
payload: { body: req.body },
});
return;
}
const result = { en: {}, de: {}, es: {}, fr: {}, it: {} };
const langKeys = Object.keys(result);
createObjectFromKeys({ data: req.body.data, result });
const request = https.get(`${DefaultSettings.httpLibEndpoint}/authorize/validate/${username}/${token}`, (response) => {
cache[req.projectCache].langData = req.body.data;
let data = '';
langKeys.forEach((langRef, index) => {
const fileContent = result[langRef];
const fileLocation = path.join(req.i18nFileBaseLocation, `${langRef}.json`);
const exists = fs.existsSync(fileLocation);
if (!exists) {
res.status(404).send({ status: 'STATUS:FAILED', data: 'File not found', payload: { fileLocation } });
return;
}
fs.writeFileSync(fileLocation, JSON.stringify(fileContent));
if (index === langKeys.length - 1) {
res.send({ status: 'STATUS:SUCCESSFUL' });
}
response.on('data', (chunk) => {
data += chunk;
});
response.on('end', () => {
data = JSON.parse(data);
if (!data && data.status !== 'STATUS:SUCCESSFUL') {
return;
}
const result = {
en: {},
de: {},
es: {},
fr: {},
it: {},
};
const langKeys = Object.keys(result);
createObjectFromKeys({
data: req.body.data,
result,
});
cache[req.projectCache].langData = req.body.data;
langKeys.forEach((langRef, index) => {
const fileContent = result[langRef];
const fileLocation = path.join(req.i18nFileBaseLocation, `${langRef}.json`);
const exists = fs.existsSync(fileLocation);
if (!exists) {
res.status(404).send({
status: 'STATUS:FAILED',
data: 'File not found',
payload: { fileLocation },
});
return;
}
fs.writeFileSync(fileLocation, JSON.stringify(fileContent));
if (index === langKeys.length - 1) {
res.send({ status: 'STATUS:SUCCESSFUL' });
}
});
});
});
request.on('error', (error) => {
console.log('error at validating login token', error);
request.abort();
res.send({
status: 'STATUS:FAILED',
step: 'UPDATE_LANG',
payload: { error },
});
return;
});
});
app.get('/api/*', (req, res) => {
......@@ -290,7 +372,9 @@ const buildCache = () => {
console.log(`* Fetching unused keys`);
const unusedKeysStart = new Date().getTime();
cache[projectName].unused = getUnusedKeys({
params: {}, projectAppLocation: projectAppLocation[projectName], i18nFileBaseLocation: i18nFileBaseLocation[projectName],
params: {},
projectAppLocation: projectAppLocation[projectName],
i18nFileBaseLocation: i18nFileBaseLocation[projectName],
});
const unusedKeysEnd = new Date().getTime();
console.log(`-- Done. Took ${unusedKeysEnd - unusedKeysStart}ms`);
......
......@@ -111,7 +111,7 @@ describe('LoginComponent', () => {
spyOn(router, 'navigateByUrl').and.callFake(() => {});
await component.login();
expect(component['authorizationFailed']).toBeFalsy();
expect(component['_authorizationFailed']).toBeFalsy();
}));
......@@ -127,7 +127,7 @@ describe('LoginComponent', () => {
spyOn(router, 'navigateByUrl').and.callFake(() => {});
await component.login();
expect(component['authorizationFailed']).toBeTruthy();
expect(component['_authorizationFailed']).toBeTruthy();
}));
});
});
......@@ -11,11 +11,16 @@ import { UserService } from '../../service/user/user.service';
})
export class LoginComponent implements OnInit {
public static readonly TYPE = 'LoginComponent';
public username = '';
public password = '';
private _authorizationFailed = false;
get authorizationFailed(): boolean {
return this._authorizationFailed;
}
private username = '';
private password = '';
private return = '';
private authorizationFailed = false;
constructor(
private userService: UserService,
......@@ -34,7 +39,7 @@ export class LoginComponent implements OnInit {
}
public async login(): Promise<void> {
this.authorizationFailed = false;
this._authorizationFailed = false;
if (this.username && this.password) {
const passwordHash = this.userService.hashPassword(this.username, this.password);
......@@ -43,7 +48,7 @@ export class LoginComponent implements OnInit {
if (isAuthenticated) {
this.router.navigateByUrl(this.return);
} else {
this.authorizationFailed = true;
this._authorizationFailed = true;
}
}
}
......
......@@ -54,12 +54,11 @@ export class ThemeSwitcherComponent {
if (isPlatformBrowser(this.platformId)) {
const themeDataset = document.getElementsByTagName('html').item(0).dataset['theme'];
if (themeDataset === this.previewThemeBackup) {
if (themeDataset !== this.previewThemeBackup) {
document.getElementsByTagName('html').item(0).dataset['theme'] = this.previewThemeBackup;
this.themesService.reloadLinkNodes(this.previewThemeBackup);
return;
}
this.themesService.reloadLinkNodes(this.previewThemeBackup);
}
}
......
......@@ -9,9 +9,11 @@ import { DefaultSettings } from '../../../../lib/default.settings';
})
export class ThemesApiService {
constructor(
private http: HttpClient,
) { }
constructor(private http: HttpClient) { }
public THEMES_PREVIEW_GET_URL(id: string, langRef: string): string {
return `/assets/images/theme/${id}/preview_${langRef}.jpeg`;
}
public THEMES_GET_URL(): string {
return `${DefaultSettings.httpApiEndpoint}/themes`;
......
import { Component, EventEmitter, OnDestroy, Output } from '@angular/core';
import { TranslateService } from '@ngx-translate/core';
import { ThemesApiService } from '../service/api/themes/themes-api.service';
import { ThemesService } from '../service/themes/themes.service';
import { CategoryType, TrackingService } from '../service/tracking/tracking.service';
......@@ -19,6 +20,7 @@ export class ThemesComponent implements OnDestroy {
private translateService: TranslateService,
public themesService: ThemesService,
private trackingService: TrackingService,
private themesApiService: ThemesApiService,
) {
this._currentTheme = this.themesService.currentTheme;
}
......@@ -32,7 +34,7 @@ export class ThemesComponent implements OnDestroy {
}
public getThemePreviewUrl(id: string): string {
return `/assets/images/theme/${id}/preview_${this.translateService.currentLang}.jpeg`;
return this.themesApiService.THEMES_PREVIEW_GET_URL(id, this.translateService.currentLang);
}
public change(id: string): void {
......
......@@ -127,14 +127,12 @@
@each $theme, $map in $themes {
html[data-theme=#{$theme}] {
@include transitionEffect(map-get($map, "background-color"));
*:fullscreen,
:-webkit-full-screen,
*:-webkit-full-screen,
*:-moz-full-screen,
body {
@include transitionEffect(map-get($map, "background-color"));
}
@include introjs($map);
......@@ -162,7 +160,12 @@
}
}
#theme-wrapper {
background-color: map-get($map, "background-color");
@include transitionEffect(map-get($map, "background-color"));
#content-container {
@include transitionEffect(map-get($map, "background-color"));
}
.text-light, a.text-light, a:hover.text-light, a:active.text-light, a:visited.text-light, a:focus.text-light {
color: map-get($map, "fg-text-light");
}
......@@ -208,7 +211,6 @@
@include transitionEffect(map-get($map, "bg-nav-row"));
.nav-row {
@include transitionEffect(map-get($map, "bg-nav-row"));
background-color: map-get($map, "bg-nav-row");
}
}
*::-webkit-input-placeholder {
......
......@@ -73,7 +73,8 @@
</head>
<body>
<div id="theme-wrapper">
<div id="theme-wrapper"
class="overflow-auto">
<div id="content">
<app-root>
<div id="loader-wrapper">
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment