fix: Move tokens from session to cookie/store

This commit is contained in:
Andras Bacsai 2022-02-21 12:35:20 +01:00
parent 954a265965
commit fcfa8717a5
10 changed files with 108 additions and 88 deletions

View File

@ -17,8 +17,6 @@ export const handle = handleSession(
let response; let response;
try { try {
if (event.locals.cookies) { if (event.locals.cookies) {
let gitlabToken = event.locals.cookies.gitlabToken || null;
let ghToken = event.locals.cookies.ghToken || null;
if (event.locals.cookies['kit.session']) { if (event.locals.cookies['kit.session']) {
const { permission, teamId, userId } = await getUserDetails(event, false); const { permission, teamId, userId } = await getUserDetails(event, false);
const newSession = { const newSession = {
@ -26,9 +24,7 @@ export const handle = handleSession(
teamId, teamId,
permission, permission,
isAdmin: permission === 'admin' || permission === 'owner', isAdmin: permission === 'admin' || permission === 'owner',
expires: event.locals.session.data.expires, expires: event.locals.session.data.expires
gitlabToken,
ghToken
}; };
if (JSON.stringify(event.locals.session.data) !== JSON.stringify(newSession)) { if (JSON.stringify(event.locals.session.data) !== JSON.stringify(newSession)) {

View File

@ -9,22 +9,6 @@ export const dateOptions: DateTimeFormatOptions = {
hour12: false hour12: false
}; };
export async function getGithubToken({ apiUrl, application, githubToken }): Promise<void> {
const response = await fetch(
`${apiUrl}/app/installations/${application.gitSource.githubApp.installationId}/access_tokens`,
{
method: 'POST',
headers: {
Authorization: `Bearer ${githubToken}`
}
}
);
if (!response.ok) {
throw new Error('Git Source not configured.');
}
const data = await response.json();
return data.token;
}
export const staticDeployments = ['react', 'vuejs', 'static', 'svelte', 'gatsby', 'php']; export const staticDeployments = ['react', 'vuejs', 'static', 'svelte', 'gatsby', 'php'];
export const notNodeDeployments = ['php', 'docker', 'rust']; export const notNodeDeployments = ['php', 'docker', 'rust'];

6
src/lib/store.ts Normal file
View File

@ -0,0 +1,6 @@
import { writable } from 'svelte/store';
export const gitTokens = writable({
githubToken: null,
gitlabToken: null
});

View File

@ -17,13 +17,20 @@
const endpoint = `/applications/${params.id}.json`; const endpoint = `/applications/${params.id}.json`;
const res = await fetch(endpoint); const res = await fetch(endpoint);
if (res.ok) { if (res.ok) {
const { application, isRunning, appId } = await res.json(); let { application, isRunning, appId, githubToken, gitlabToken } = await res.json();
if (!application || Object.entries(application).length === 0) { if (!application || Object.entries(application).length === 0) {
return { return {
status: 302, status: 302,
redirect: '/applications' redirect: '/applications'
}; };
} }
if (application.gitSource?.githubAppId && !githubToken) {
const response = await fetch(`/applications/${params.id}/configuration/githubToken.json`);
if (response.ok) {
const { token } = await response.json();
githubToken = token;
}
}
const configurationPhase = checkConfiguration(application); const configurationPhase = checkConfiguration(application);
if ( if (
configurationPhase && configurationPhase &&
@ -38,7 +45,9 @@
return { return {
props: { props: {
application, application,
isRunning isRunning,
githubToken,
gitlabToken
}, },
stuff: { stuff: {
isRunning, isRunning,
@ -58,12 +67,18 @@
<script lang="ts"> <script lang="ts">
export let application; export let application;
export let isRunning; export let isRunning;
export let githubToken;
export let gitlabToken;
import { page, session } from '$app/stores'; import { page, session } from '$app/stores';
import { errorNotification } from '$lib/form'; import { errorNotification } from '$lib/form';
import DeleteIcon from '$lib/components/DeleteIcon.svelte'; import DeleteIcon from '$lib/components/DeleteIcon.svelte';
import Loading from '$lib/components/Loading.svelte'; import Loading from '$lib/components/Loading.svelte';
import { del, post } from '$lib/api'; import { del, post } from '$lib/api';
import { goto } from '$app/navigation'; import { goto } from '$app/navigation';
import { gitTokens } from '$lib/store';
if (githubToken) $gitTokens.githubToken = githubToken;
if (gitlabToken) $gitTokens.gitlabToken = gitlabToken;
let loading = false; let loading = false;
const { id } = $page.params; const { id } = $page.params;

View File

@ -1,14 +1,12 @@
<script lang="ts"> <script lang="ts">
import { browser } from '$app/env';
import { goto } from '$app/navigation';
export let application; export let application;
import { page, session } from '$app/stores'; import { goto } from '$app/navigation';
import { page } from '$app/stores';
import { get, post } from '$lib/api'; import { get, post } from '$lib/api';
import { errorNotification } from '$lib/form'; import { errorNotification } from '$lib/form';
import { onMount } from 'svelte'; import { onMount } from 'svelte';
import { gitTokens } from '$lib/store';
const { id } = $page.params; const { id } = $page.params;
const from = $page.url.searchParams.get('from'); const from = $page.url.searchParams.get('from');
@ -32,7 +30,7 @@
let showSave = false; let showSave = false;
async function loadRepositoriesByPage(page = 0) { async function loadRepositoriesByPage(page = 0) {
return await get(`${apiUrl}/installation/repositories?per_page=100&page=${page}`, { return await get(`${apiUrl}/installation/repositories?per_page=100&page=${page}`, {
Authorization: `token ${$session.ghToken}` Authorization: `token ${$gitTokens.githubToken}`
}); });
} }
async function loadRepositories() { async function loadRepositories() {
@ -56,7 +54,7 @@
selected.projectId = repositories.find((repo) => repo.full_name === selected.repository).id; selected.projectId = repositories.find((repo) => repo.full_name === selected.repository).id;
try { try {
branches = await get(`${apiUrl}/repos/${selected.repository}/branches`, { branches = await get(`${apiUrl}/repos/${selected.repository}/branches`, {
Authorization: `token ${$session.ghToken}` Authorization: `token ${$gitTokens.githubToken}`
}); });
return; return;
} catch ({ error }) { } catch ({ error }) {
@ -84,6 +82,10 @@
onMount(async () => { onMount(async () => {
try { try {
if (!$gitTokens.githubToken) {
const { token } = await get(`/applications/${id}/configuration/githubToken.json`);
$gitTokens.githubToken = token;
}
await loadRepositories(); await loadRepositories();
} catch (error) { } catch (error) {
if ( if (
@ -114,7 +116,9 @@
} }
} }
if (error.message === 'Bad credentials') { if (error.message === 'Bad credentials') {
browser && window.location.reload(); const { token } = await get(`/applications/${id}/configuration/githubToken.json`);
$gitTokens.githubToken = token;
return await loadRepositories();
} }
return errorNotification(error); return errorNotification(error);
} }

View File

@ -8,6 +8,8 @@
import cuid from 'cuid'; import cuid from 'cuid';
import { goto } from '$app/navigation'; import { goto } from '$app/navigation';
import { del, get, post, put } from '$lib/api'; import { del, get, post, put } from '$lib/api';
import { gitTokens } from '$lib/store';
const { id } = $page.params; const { id } = $page.params;
const from = $page.url.searchParams.get('from'); const from = $page.url.searchParams.get('from');
@ -35,13 +37,13 @@
branch: undefined branch: undefined
}; };
onMount(async () => { onMount(async () => {
if (!$session.gitlabToken) { if (!$gitTokens.gitlabToken) {
getGitlabToken(); getGitlabToken();
} else { } else {
loading.base = true; loading.base = true;
try { try {
const user = await get(`${apiUrl}/v4/user`, { const user = await get(`${apiUrl}/v4/user`, {
Authorization: `Bearer ${$session.gitlabToken}` Authorization: `Bearer ${$gitTokens.gitlabToken}`
}); });
username = user.username; username = user.username;
} catch (error) { } catch (error) {
@ -49,7 +51,7 @@
} }
try { try {
groups = await get(`${apiUrl}/v4/groups?per_page=5000`, { groups = await get(`${apiUrl}/v4/groups?per_page=5000`, {
Authorization: `Bearer ${$session.gitlabToken}` Authorization: `Bearer ${$gitTokens.gitlabToken}`
}); });
} catch (error) { } catch (error) {
errorNotification(error); errorNotification(error);
@ -87,7 +89,7 @@
projects = await get( projects = await get(
`${apiUrl}/v4/users/${selected.group.name}/projects?min_access_level=40&page=1&per_page=25&archived=false`, `${apiUrl}/v4/users/${selected.group.name}/projects?min_access_level=40&page=1&per_page=25&archived=false`,
{ {
Authorization: `Bearer ${$session.gitlabToken}` Authorization: `Bearer ${$gitTokens.gitlabToken}`
} }
); );
} catch (error) { } catch (error) {
@ -101,7 +103,7 @@
projects = await get( projects = await get(
`${apiUrl}/v4/groups/${selected.group.id}/projects?page=1&per_page=25&archived=false`, `${apiUrl}/v4/groups/${selected.group.id}/projects?page=1&per_page=25&archived=false`,
{ {
Authorization: `Bearer ${$session.gitlabToken}` Authorization: `Bearer ${$gitTokens.gitlabToken}`
} }
); );
} catch (error) { } catch (error) {
@ -119,7 +121,7 @@
branches = await get( branches = await get(
`${apiUrl}/v4/projects/${selected.project.id}/repository/branches?per_page=100&page=1`, `${apiUrl}/v4/projects/${selected.project.id}/repository/branches?per_page=100&page=1`,
{ {
Authorization: `Bearer ${$session.gitlabToken}` Authorization: `Bearer ${$gitTokens.gitlabToken}`
} }
); );
} catch (error) { } catch (error) {
@ -169,7 +171,7 @@
merge_requests_events: true merge_requests_events: true
}, },
{ {
Authorization: `Bearer ${$session.gitlabToken}` Authorization: `Bearer ${$gitTokens.gitlabToken}`
} }
); );
} catch (error) { } catch (error) {
@ -193,7 +195,7 @@
publicSshKey = publicKey; publicSshKey = publicKey;
} }
const deployKeys = await get(deployKeyUrl, { const deployKeys = await get(deployKeyUrl, {
Authorization: `Bearer ${$session.gitlabToken}` Authorization: `Bearer ${$gitTokens.gitlabToken}`
}); });
const deployKeyFound = deployKeys.filter((dk) => dk.title === `${appId}-coolify-deploy-key`); const deployKeyFound = deployKeys.filter((dk) => dk.title === `${appId}-coolify-deploy-key`);
if (deployKeyFound.length > 0) { if (deployKeyFound.length > 0) {
@ -202,7 +204,7 @@
`${deployKeyUrl}/${deployKey.id}`, `${deployKeyUrl}/${deployKey.id}`,
{}, {},
{ {
Authorization: `Bearer ${$session.gitlabToken}` Authorization: `Bearer ${$gitTokens.gitlabToken}`
} }
); );
} }
@ -215,7 +217,7 @@
can_push: false can_push: false
}, },
{ {
Authorization: `Bearer ${$session.gitlabToken}` Authorization: `Bearer ${$gitTokens.gitlabToken}`
} }
); );
await post(updateDeployKeyIdUrl, { deployKeyId: id }); await post(updateDeployKeyIdUrl, { deployKeyId: id });

View File

@ -34,6 +34,7 @@
import { page, session } from '$app/stores'; import { page, session } from '$app/stores';
import { get } from '$lib/api'; import { get } from '$lib/api';
import { errorNotification } from '$lib/form'; import { errorNotification } from '$lib/form';
import { gitTokens } from '$lib/store';
let scanning = true; let scanning = true;
let foundConfig = null; let foundConfig = null;
@ -59,7 +60,7 @@
try { try {
if (type === 'gitlab') { if (type === 'gitlab') {
const files = await get(`${apiUrl}/v4/projects/${projectId}/repository/tree`, { const files = await get(`${apiUrl}/v4/projects/${projectId}/repository/tree`, {
Authorization: `Bearer ${$session.gitlabToken}` Authorization: `Bearer ${$gitTokens.gitlabToken}`
}); });
const packageJson = files.find( const packageJson = files.find(
(file) => file.name === 'package.json' && file.type === 'blob' (file) => file.name === 'package.json' && file.type === 'blob'
@ -78,7 +79,7 @@
const data = await get( const data = await get(
`${apiUrl}/v4/projects/${projectId}/repository/files/${path}/raw?ref=${branch}`, `${apiUrl}/v4/projects/${projectId}/repository/files/${path}/raw?ref=${branch}`,
{ {
Authorization: `Bearer ${$session.gitlabToken}` Authorization: `Bearer ${$gitTokens.gitlabToken}`
} }
); );
const json = JSON.parse(data) || {}; const json = JSON.parse(data) || {};
@ -94,7 +95,7 @@
} }
} else if (type === 'github') { } else if (type === 'github') {
const files = await get(`${apiUrl}/repos/${repository}/contents?ref=${branch}`, { const files = await get(`${apiUrl}/repos/${repository}/contents?ref=${branch}`, {
Authorization: `Bearer ${$session.ghToken}`, Authorization: `Bearer ${$gitTokens.githubToken}`,
Accept: 'application/vnd.github.v2.json' Accept: 'application/vnd.github.v2.json'
}); });
const packageJson = files.find( const packageJson = files.find(
@ -111,7 +112,7 @@
foundConfig.buildPack = 'docker'; foundConfig.buildPack = 'docker';
} else if (packageJson) { } else if (packageJson) {
const data = await get(`${packageJson.git_url}`, { const data = await get(`${packageJson.git_url}`, {
Authorization: `Bearer ${$session.ghToken}`, Authorization: `Bearer ${$gitTokens.githubToken}`,
Accept: 'application/vnd.github.v2.raw' Accept: 'application/vnd.github.v2.raw'
}); });
const json = JSON.parse(data) || {}; const json = JSON.parse(data) || {};

View File

@ -0,0 +1,45 @@
import { getUserDetails } from '$lib/common';
import * as db from '$lib/database';
import { ErrorHandler } from '$lib/database';
import type { RequestHandler } from '@sveltejs/kit';
import jsonwebtoken from 'jsonwebtoken';
export const get: RequestHandler = async (event) => {
const { teamId, status, body } = await getUserDetails(event);
if (status === 401) return { status, body };
const { id } = event.params;
try {
const application = await db.getApplication({ id, teamId });
const payload = {
iat: Math.round(new Date().getTime() / 1000),
exp: Math.round(new Date().getTime() / 1000 + 60),
iss: application.gitSource.githubApp.appId
};
const githubToken = jsonwebtoken.sign(payload, application.gitSource.githubApp.privateKey, {
algorithm: 'RS256'
});
const response = await fetch(
`${application.gitSource.apiUrl}/app/installations/${application.gitSource.githubApp.installationId}/access_tokens`,
{
method: 'POST',
headers: {
Authorization: `Bearer ${githubToken}`
}
}
);
if (!response.ok) {
throw new Error(`${response.status} ${response.statusText}`);
}
const data = await response.json();
return {
status: 201,
body: { token: data.token },
headers: {
'Set-Cookie': `githubToken=${data.token}; Path=/; HttpOnly; Max-Age=15778800;`
}
};
} catch (error) {
return ErrorHandler(error);
}
};

View File

@ -20,6 +20,7 @@
<script lang="ts"> <script lang="ts">
export let application; export let application;
export let appId; export let appId;
import GithubRepositories from './_GithubRepositories.svelte'; import GithubRepositories from './_GithubRepositories.svelte';
import GitlabRepositories from './_GitlabRepositories.svelte'; import GitlabRepositories from './_GitlabRepositories.svelte';
</script> </script>

View File

@ -1,5 +1,4 @@
import { getUserDetails } from '$lib/common'; import { getUserDetails } from '$lib/common';
import { getGithubToken } from '$lib/components/common';
import * as db from '$lib/database'; import * as db from '$lib/database';
import { ErrorHandler } from '$lib/database'; import { ErrorHandler } from '$lib/database';
import { checkContainer } from '$lib/haproxy'; import { checkContainer } from '$lib/haproxy';
@ -11,61 +10,28 @@ export const get: RequestHandler = async (event) => {
const { teamId, status, body } = await getUserDetails(event); const { teamId, status, body } = await getUserDetails(event);
if (status === 401) return { status, body }; if (status === 401) return { status, body };
const appId = process.env['COOLIFY_APP_ID'];
let githubToken = null;
let ghToken = null;
let isRunning = false;
const { id } = event.params; const { id } = event.params;
const appId = process.env['COOLIFY_APP_ID'];
let isRunning = false;
let githubToken = event.locals.cookies?.githubToken || null;
let gitlabToken = event.locals.cookies?.gitlabToken || null;
try { try {
const application = await db.getApplication({ id, teamId }); const application = await db.getApplication({ id, teamId });
const { gitSource } = application;
if (gitSource?.type === 'github' && gitSource?.githubApp) {
if (!event.locals.session.data.ghToken) {
const payload = {
iat: Math.round(new Date().getTime() / 1000),
exp: Math.round(new Date().getTime() / 1000 + 600),
iss: gitSource.githubApp.appId
};
githubToken = jsonwebtoken.sign(payload, gitSource.githubApp.privateKey, {
algorithm: 'RS256'
});
ghToken = await getGithubToken({ apiUrl: gitSource.apiUrl, application, githubToken });
} else {
try {
await getRequest(`${gitSource.apiUrl}/installation/repositories`, {
Authorization: `token ${event.locals.session.data.ghToken}`
});
} catch (error) {
const payload = {
iat: Math.round(new Date().getTime() / 1000),
exp: Math.round(new Date().getTime() / 1000 + 600),
iss: gitSource.githubApp.appId
};
githubToken = jsonwebtoken.sign(payload, gitSource.githubApp.privateKey, {
algorithm: 'RS256'
});
ghToken = await getGithubToken({ apiUrl: gitSource.apiUrl, application, githubToken });
}
}
}
if (application.destinationDockerId) { if (application.destinationDockerId) {
isRunning = await checkContainer(application.destinationDocker.engine, id); isRunning = await checkContainer(application.destinationDocker.engine, id);
} }
const payload = { return {
status: 200,
body: { body: {
isRunning, isRunning,
application, application,
appId appId,
githubToken,
gitlabToken
}, },
headers: {} headers: {}
}; };
if (ghToken) {
payload.headers = {
'set-cookie': [`ghToken=${ghToken}; HttpOnly; Path=/; Max-Age=15778800;`]
};
}
return payload;
} catch (error) { } catch (error) {
console.log(error); console.log(error);
return ErrorHandler(error); return ErrorHandler(error);