Skip to content

Commit

Permalink
Feature: API key encrpytion (#25)
Browse files Browse the repository at this point in the history
* Add api key encryption secret input field

* Add api secret input to storage

* Add function to use encrypted api key

* Show icon fetch error in dropdown

* Add script to encrypt api key

* Add instructions for new api keys / secrets to README
  • Loading branch information
fabiankuenzer authored Aug 8, 2024
1 parent 57c206c commit 1f50187
Show file tree
Hide file tree
Showing 9 changed files with 79 additions and 4 deletions.
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,12 @@ After running the following command, the developer console is available with rig

⚠️ Mac App Store builds of Office do not support the OfficeWebAddinDeveloperExtras flag.

## API key encryption
If the Freepik API key or the API key secret is renewed, three steps must be executed:
1. Update the API key or the secret in 1Password.
2. Encrypt the API key by running `node encryptApiKey.ts` in the root directory of this repository.
3. Update the `const encryptedFreepikApiKey` in the `src/taskpane/encryptionUtils.ts` file with the encrypted API key.

## Helpful links

[PowerPoint Api-Documentation](https://learn.microsoft.com/de-de/javascript/api/powerpoint?view=powerpoint-js-preview)
Expand Down
14 changes: 14 additions & 0 deletions encryptApiKey.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
const readline = require("readline");
const cryptoJS = require("crypto-js");

const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
});

rl.question("Enter Freepik API key from 1Password: ", (apiKey) => {
rl.question("Enter API key secret from 1Password: ", (secret) => {
console.log(`Encrypted API key: ${cryptoJS.AES.encrypt(apiKey, secret).toString()}`);
rl.close();
});
});
13 changes: 13 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,13 @@
},
"dependencies": {
"core-js": "^3.9.1",
"crypto-js": "^4.2.0",
"regenerator-runtime": "^0.13.7"
},
"devDependencies": {
"@babel/core": "^7.13.10",
"@babel/preset-typescript": "^7.13.0",
"@types/crypto-js": "^4.2.2",
"@types/office-js": "^1.0.256",
"@types/office-runtime": "^1.0.23",
"acorn": "^8.5.0",
Expand Down
17 changes: 17 additions & 0 deletions src/taskpane/encryptionUtils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import CryptoJS from "crypto-js";

const encryptedFreepikApiKey =
"U2FsdGVkX1+uakX7Pa7rDwZUW41gsxUcc5Mk1Zf9Ff/RkAhy5TT6snpzfSoe7kn+A7ulqhvrLMasq4hJ/KkwoA==";

export function storeFreepikApiKeySecret() {
document.getElementById("save-api-key-secret").onclick = () => {
const encryptionSecret = (<HTMLInputElement>document.getElementById("api-key-secret")).value;
localStorage.setItem("apiKeySecret", encryptionSecret);
(<HTMLTextAreaElement>document.getElementById("api-key-secret")).value = "API key secret stored";
};
}

export function getDecryptedFreepikApiKey(): string {
const decryptedBytes = CryptoJS.AES.decrypt(encryptedFreepikApiKey, localStorage.getItem("apiKeySecret"));
return decryptedBytes.toString(CryptoJS.enc.Utf8);
}
19 changes: 17 additions & 2 deletions src/taskpane/iconDownloadUtils.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import { FetchIconResponse } from "./types";
import { getDecryptedFreepikApiKey } from "./encryptionUtils";
import { initDropdownPlaceholder } from "./taskpane";

export async function fetchIcons(searchTerm: string): Promise<Array<FetchIconResponse>> {
const url = `https://hammerhead-app-fj5ps.ondigitalocean.app/icons?term=${searchTerm}&family-id=300&filters[shape]=outline&filters[color]=solid-black&filters[free_svg]=premium`;
const requestHeaders = new Headers();
requestHeaders.append("X-Freepik-API-Key", "XXX");
requestHeaders.append("X-Freepik-API-Key", getDecryptedFreepikApiKey());
const requestOptions = {
method: "GET",
headers: requestHeaders,
Expand All @@ -20,14 +22,16 @@ export async function fetchIcons(searchTerm: string): Promise<Array<FetchIconRes
}))
.slice(0, 50);
} catch (e) {
showFetchIconsErrorInDropdown();
initDropdownPlaceholder();
throw new Error("Error fetching icons: " + e);
}
}

export async function getDownloadPathForIconWith(id: string) {
const url = `https://hammerhead-app-fj5ps.ondigitalocean.app/icons/${id}/download?format=png`;
const requestHeaders = new Headers();
requestHeaders.append("X-Freepik-API-Key", "XXX");
requestHeaders.append("X-Freepik-API-Key", getDecryptedFreepikApiKey());
const requestOptions = {
method: "GET",
headers: requestHeaders,
Expand All @@ -53,3 +57,14 @@ export async function downloadIconWith(url: string) {
throw new Error("Error downloading icon: " + e);
}
}

function showFetchIconsErrorInDropdown() {
const iconPreviewElement = document.getElementById("icon-previews");
const spanElement = document.createElement("span");
spanElement.innerText = "Error fetching icons";
const anchorElement = document.createElement("a");
const listElement = document.createElement("li");
iconPreviewElement.appendChild(listElement);
listElement.appendChild(anchorElement);
anchorElement.appendChild(spanElement);
}
1 change: 1 addition & 0 deletions src/taskpane/taskpane.css
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ ul {
}

.ms-welcome__main {
background-color: white;
display: -webkit-flex;
display: flex;
-webkit-flex-direction: column;
Expand Down
6 changes: 6 additions & 0 deletions src/taskpane/taskpane.html
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,12 @@ <h1 class="ms-font-su">L21s Add-In</h1>
</header>
<section id="display-msg" class="ms-welcome__main">
<div class="padding">
<h3 class="ms-font-l">API Key Secret</h3>
<div>
<input type="text" id="api-key-secret" value="Enter API key secret from 1Password"/>
<button class="save-button btn" id="save-api-key-secret">Save</button>
</div>

<h3 class="ms-font-l">Sticker</h3>
<div class="input-field">
<label for="initials">Autor</label><input type="text" id="initials" />
Expand Down
5 changes: 3 additions & 2 deletions src/taskpane/taskpane.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import * as M from "../../lib/materialize/js/materialize.min";
import { runPowerPoint } from "./powerPointUtil";
import { columnLineName, rowLineName, createColumns, createRows } from "./rowsColumns";
import { getDownloadPathForIconWith, downloadIconWith, fetchIcons } from "./iconDownloadUtils";
import { storeFreepikApiKeySecret } from "./encryptionUtils";
import { FetchIconResponse } from "./types";

Office.onReady((info) => {
Expand All @@ -18,6 +19,7 @@ Office.onReady((info) => {

let initials = <HTMLInputElement>document.getElementById("initials");
initials.value = localStorage.getItem("initials");
storeFreepikApiKeySecret();

document.getElementById("fill-background").onclick = async () => {
const colorPicker = <HTMLInputElement>document.getElementById("background-color");
Expand Down Expand Up @@ -221,11 +223,10 @@ function insertIconOnClickOnPreview() {
document.getElementById("icon-previews").addEventListener("click", (event) => insertBase64ImageOn(event), false);
}

function initDropdownPlaceholder() {
export function initDropdownPlaceholder() {
const iconPreviewElement = document.getElementById("icon-previews");
for (let i = 0; i < 15; i++) {
const spanElement = document.createElement("span");
spanElement.innerText = "Loading...";
const anchorElement = document.createElement("a");
const listElement = document.createElement("li");
iconPreviewElement.appendChild(listElement);
Expand Down

0 comments on commit 1f50187

Please sign in to comment.