Compare commits
4 Commits
a1cba242e9
...
bright
| Author | SHA1 | Date | |
|---|---|---|---|
| cbcf6a731b | |||
| 9ad8e1aa9b | |||
| bea1c3aba3 | |||
| 5c8a3f56dd |
5
.dockerignore
Normal file
5
.dockerignore
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
**/node_modules
|
||||||
|
.git
|
||||||
|
.gitignore
|
||||||
|
.local
|
||||||
|
.env*
|
||||||
29
.idea/dataSources.local.xml
generated
Normal file
29
.idea/dataSources.local.xml
generated
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="dataSourceStorageLocal" created-in="WS-241.15989.105">
|
||||||
|
<data-source name="devblog@mysql-server" uuid="32913bc6-cafd-416c-b070-eee7c73cf755">
|
||||||
|
<database-info product="MySQL" version="8.3.0" jdbc-version="4.2" driver-name="MySQL Connector/J" driver-version="mysql-connector-java-8.0.25 (Revision: 08be9e9b4cba6aa115f9b27b215887af40b159e0)" dbms="MYSQL" exact-version="8.3.0" exact-driver-version="8.0">
|
||||||
|
<extra-name-characters>#@</extra-name-characters>
|
||||||
|
<identifier-quote-string>`</identifier-quote-string>
|
||||||
|
</database-info>
|
||||||
|
<case-sensitivity plain-identifiers="exact" quoted-identifiers="exact" />
|
||||||
|
<secret-storage>master_key</secret-storage>
|
||||||
|
<user-name>devblog</user-name>
|
||||||
|
<schema-mapping>
|
||||||
|
<introspection-scope>
|
||||||
|
<node kind="schema">
|
||||||
|
<name qname="@" />
|
||||||
|
<name qname="devblog" />
|
||||||
|
</node>
|
||||||
|
</introspection-scope>
|
||||||
|
</schema-mapping>
|
||||||
|
<ssl-config use-ide-store="true" use-java-store="true" use-system-store="true">
|
||||||
|
<ca-cert>$USER_HOME$/Documents/db-ssl/ca.crt</ca-cert>
|
||||||
|
<client-cert>$USER_HOME$/Documents/db-ssl/mbp.crt</client-cert>
|
||||||
|
<client-key>$USER_HOME$/Documents/db-ssl/mbp.key</client-key>
|
||||||
|
<enabled>true</enabled>
|
||||||
|
<mode>VERIFY_FULL</mode>
|
||||||
|
</ssl-config>
|
||||||
|
</data-source>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
12
.idea/dataSources.xml
generated
Normal file
12
.idea/dataSources.xml
generated
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="DataSourceManagerImpl" format="xml" multifile-model="true">
|
||||||
|
<data-source source="LOCAL" name="devblog@mysql-server" uuid="32913bc6-cafd-416c-b070-eee7c73cf755">
|
||||||
|
<driver-ref>mysql.8</driver-ref>
|
||||||
|
<synchronize>true</synchronize>
|
||||||
|
<jdbc-driver>com.mysql.cj.jdbc.Driver</jdbc-driver>
|
||||||
|
<jdbc-url>jdbc:mysql://mysql-server.suyono.dev:13306/devblog</jdbc-url>
|
||||||
|
<working-dir>$ProjectFileDir$</working-dir>
|
||||||
|
</data-source>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
6
.idea/sqldialects.xml
generated
Normal file
6
.idea/sqldialects.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="SqlDialectMappings">
|
||||||
|
<file url="PROJECT" dialect="MySQL" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
@@ -1,2 +1,2 @@
|
|||||||
nodejs 20.8.1
|
nodejs 20.12.2
|
||||||
pnpm 8.9.2
|
pnpm 9.0.6
|
||||||
|
|||||||
37
Dockerfile
Normal file
37
Dockerfile
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
FROM node:lts-alpine as base
|
||||||
|
|
||||||
|
FROM base as builder
|
||||||
|
USER 1000:1000
|
||||||
|
ADD --chown=1000:1000 . /home/node/nextts
|
||||||
|
WORKDIR /home/node/nextts
|
||||||
|
RUN wget -qO- https://get.pnpm.io/install.sh | PNPM_VERSION="8.15.3" ENV="/home/node/.shrc" SHELL="$(which sh)" sh -
|
||||||
|
ENV PATH=/home/node/.local/share/pnpm:$PATH
|
||||||
|
RUN pnpm install && pnpm run build
|
||||||
|
|
||||||
|
FROM base as runtime
|
||||||
|
|
||||||
|
RUN npm install -g pm2
|
||||||
|
|
||||||
|
|
||||||
|
COPY --from=builder /home/node/nextts/public /home/node/nextts/public
|
||||||
|
COPY --from=builder /home/node/nextts/.next/standalone /home/node/nextts
|
||||||
|
COPY --from=builder /home/node/nextts/.next/static /home/node/nextts/.next/static
|
||||||
|
ADD --chown=1000:1000 pm2.config.js /home/node/nextts/
|
||||||
|
ADD --chown=1000:1000 dummies /home/node/nextts/dummies
|
||||||
|
|
||||||
|
RUN chown -R 1000:1000 /home/node/nextts
|
||||||
|
|
||||||
|
USER 1000:1000
|
||||||
|
WORKDIR /home/node/nextts
|
||||||
|
|
||||||
|
ENV PORT 3000
|
||||||
|
ENV NODE_ENV production
|
||||||
|
ENV HOME /home/node
|
||||||
|
ENV HOSTNAME "0.0.0.0"
|
||||||
|
|
||||||
|
RUN wget -qO- https://get.pnpm.io/install.sh | PNPM_VERSION="8.15.3" ENV="/home/node/.shrc" SHELL="$(which sh)" sh -
|
||||||
|
ENV PATH=/home/node/.local/share/pnpm:$PATH
|
||||||
|
RUN pnpm install
|
||||||
|
|
||||||
|
CMD ["pm2-runtime", "pm2.config.js"]
|
||||||
|
#CMD ["node", "server.js"]
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
import { promisePool } from "@/backend/db";
|
import { getPromisePool } from "@/backend/db";
|
||||||
import {RowDataPacket} from "mysql2";
|
import {RowDataPacket} from "mysql2";
|
||||||
|
|
||||||
async function query() {
|
async function query() {
|
||||||
try {
|
try {
|
||||||
const [rows, fields] = await promisePool.query<RowDataPacket[]>('select slug from post limit 1;')
|
const [rows, fields] = await getPromisePool().query<RowDataPacket[]>('select slug from post limit 1;')
|
||||||
return(rows[0]['slug'] as string)
|
return(rows[0]['slug'] as string)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log(e)
|
console.log(e)
|
||||||
@@ -11,11 +11,18 @@ async function query() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default async function DbCheck() {
|
export default async function DbCheck({ searchParams }: { searchParams: { [key: string]: string | string[] | undefined }}) {
|
||||||
|
let flag = "empty";
|
||||||
|
|
||||||
|
if (typeof searchParams["flag"] === 'string') {
|
||||||
|
flag = searchParams["flag"]
|
||||||
|
}
|
||||||
|
|
||||||
return(
|
return(
|
||||||
<div className={`flex flex-col`}>
|
<div className={`flex flex-col`}>
|
||||||
<p>Env: { process.env.MYSQL_HOST }</p>
|
<p>Env: { process.env.MYSQL_HOST }</p>
|
||||||
<p>Result: { await query() }</p>
|
<p>Result: { await query() }</p>
|
||||||
|
<p>Flag: { flag }</p>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
119
app/page.tsx
119
app/page.tsx
@@ -4,66 +4,73 @@ import { raleway, syne, questrial } from "@/app/fonts";
|
|||||||
|
|
||||||
export default function Home() {
|
export default function Home() {
|
||||||
return (
|
return (
|
||||||
<div className={`flex flex-col`}>
|
<div className={`flex flex-col`}>
|
||||||
<div className={`bg-cover bg-center flex flex-col-reverse bg-blog-cover`}>
|
<div className={`grid grid-rows-1 grid-cols-1 justify-items-center`}>
|
||||||
<div className={`bg-neutral-100 bg-opacity-30 flex flex-col py-10`}>
|
<Image
|
||||||
<p className={`${raleway.className} text-white text-center text-7xl font-thin mb-6`}>
|
src={`https://assets.suyono.me/placeholder.webp`}
|
||||||
SUYONO
|
alt={`blog cover`}
|
||||||
</p>
|
className={`object-cover col-start-1 row-start-1 w-screen h-192 z-0`}
|
||||||
<p className={`${raleway.className} text-white text-center font-thin text-xl mb-10`}>
|
width={1581}
|
||||||
A Tech Archive
|
height={759}
|
||||||
</p>
|
/>
|
||||||
</div>
|
<div className={`flex flex-col-reverse col-start-1 row-start-1 w-screen`}>
|
||||||
<div className={`h-64`}></div> {/* spacer */}
|
<div className={`bg-neutral-100 bg-opacity-30 flex flex-col py-10 z-10`}>
|
||||||
<div className={`h-64`}></div> {/* spacer */}
|
<p className={`${raleway.className} text-white text-center text-7xl font-thin mb-6`}>
|
||||||
</div>
|
SUYONO
|
||||||
<div className={`flex flex-row justify-center my-8`}>
|
|
||||||
<div className={`border border-slate-100 flex flex-col`}>
|
|
||||||
<Link
|
|
||||||
href="/post/nginx-ssl-client-certificate-verification-manage-access-to-a-site"
|
|
||||||
className={`flex flex-row max-w-4xl items-center`}
|
|
||||||
>
|
|
||||||
<Image
|
|
||||||
src="/assets/pthumb.webp"
|
|
||||||
alt="post thumbnail"
|
|
||||||
width={454}
|
|
||||||
height={341}
|
|
||||||
/>
|
|
||||||
<div className={`flex flex-col mx-10`}>
|
|
||||||
<p className={`${syne.className} text-2xl`}>
|
|
||||||
Nginx + SSL Client Certificate Verification: Manage Access to a
|
|
||||||
site
|
|
||||||
</p>
|
</p>
|
||||||
<p className={`${questrial.className} line-clamp-3 mt-4`}>
|
<p className={`${raleway.className} text-white text-center font-thin text-xl mb-10`}>
|
||||||
Access control is a fundamental part of security. Most entities
|
A Tech Archive
|
||||||
rely on the combination of username and password, sometimes with
|
|
||||||
additional multi-factor authentication to improve security. Some
|
|
||||||
entities also use the SSL client certificate verification to
|
|
||||||
manage access to specific resources. One of the use cases where
|
|
||||||
SSL client certificate verification fits perfectly is managing
|
|
||||||
access to internet-facing development or staging servers. In
|
|
||||||
this post, I'll share how to set up the certificates and
|
|
||||||
configure nginx to verify users based on their certificates.
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</Link>
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className={`flex flex-row justify-center my-8`}>
|
||||||
|
<div className={`border border-slate-100 flex flex-col`}>
|
||||||
|
<Link
|
||||||
|
href="/post/nginx-ssl-client-certificate-verification-manage-access-to-a-site"
|
||||||
|
className={`flex flex-row max-w-4xl items-center`}
|
||||||
|
>
|
||||||
|
<Image
|
||||||
|
src="https://assets.suyono.me/pthumb.webp"
|
||||||
|
alt="post thumbnail"
|
||||||
|
width={454}
|
||||||
|
height={341}
|
||||||
|
/>
|
||||||
|
<div className={`flex flex-col mx-10`}>
|
||||||
|
<p className={`${syne.className} text-2xl`}>
|
||||||
|
Nginx + SSL Client Certificate Verification: Manage Access to a
|
||||||
|
site
|
||||||
|
</p>
|
||||||
|
<p className={`${questrial.className} line-clamp-3 mt-4`}>
|
||||||
|
Access control is a fundamental part of security. Most entities
|
||||||
|
rely on the combination of username and password, sometimes with
|
||||||
|
additional multi-factor authentication to improve security. Some
|
||||||
|
entities also use the SSL client certificate verification to
|
||||||
|
manage access to specific resources. One of the use cases where
|
||||||
|
SSL client certificate verification fits perfectly is managing
|
||||||
|
access to internet-facing development or staging servers. In
|
||||||
|
this post, I'll share how to set up the certificates and
|
||||||
|
configure nginx to verify users based on their certificates.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className={`flex flex-row bg-teal-50 justify-center`}>
|
||||||
|
<div className={`max-w-4xl py-28 px-10`}>
|
||||||
|
<p className={`text-3xl ${raleway.className}`}>Hi There</p>
|
||||||
|
<p className={`text-base ${raleway.className} my-4`}>
|
||||||
|
a new take on experience is the best teacher
|
||||||
|
</p>
|
||||||
|
<p className={`${raleway.className} text-sm`}>
|
||||||
|
I started this blog as an archive of my experiences and knowledge.
|
||||||
|
By writing them out, I hope it will help me unlearn and relearn the
|
||||||
|
various knowledge and skills I've accumulated. I hope the
|
||||||
|
articles, source code examples, and server config examples I wrote
|
||||||
|
will help you somehow. Read on and enjoy!
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className={`flex flex-row bg-teal-50 justify-center`}>
|
|
||||||
<div className={`max-w-4xl py-28 px-10`}>
|
|
||||||
<p className={`text-3xl ${raleway.className}`}>Hi There</p>
|
|
||||||
<p className={`text-base ${raleway.className} my-4`}>
|
|
||||||
a new take on experience is the best teacher
|
|
||||||
</p>
|
|
||||||
<p className={`${raleway.className} text-sm`}>
|
|
||||||
I started this blog as an archive of my experiences and knowledge.
|
|
||||||
By writing them out, I hope it will help me unlearn and relearn the
|
|
||||||
various knowledge and skills I've accumulated. I hope the
|
|
||||||
articles, source code examples, and server config examples I wrote
|
|
||||||
will help you somehow. Read on and enjoy!
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,39 +1,42 @@
|
|||||||
import mysql, { PoolOptions } from "mysql2";
|
import mysql, { PoolOptions, Pool } from "mysql2";
|
||||||
|
import { Pool as pPool } from "mysql2/promise"
|
||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
import * as appEnv from "./env";
|
import * as appEnv from "./env";
|
||||||
|
|
||||||
if (typeof process.env.MYSQL_SSL_CA === 'undefined') {
|
let pool: Pool | undefined;
|
||||||
throw new Error("missing MYSQL_SSL_CA")
|
let promisePool: pPool | undefined;
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof process.env.MYSQL_SSL_KEY === 'undefined') {
|
export function getPool(): Pool {
|
||||||
throw new Error("missing MYSQL_SSL_KEY")
|
const access: PoolOptions = {
|
||||||
}
|
host: appEnv.getMysqlHost(),
|
||||||
|
port: appEnv.getMysqlPort(),
|
||||||
if (typeof process.env.MYSQL_SSL_CERT === 'undefined') {
|
user: appEnv.getMysqlUser(),
|
||||||
throw new Error("missing MYSQL_SSL_CERT")
|
password: appEnv.getMysqlPassword(),
|
||||||
}
|
database: appEnv.getMysqlDatabase(),
|
||||||
|
waitForConnections: true,
|
||||||
const access: PoolOptions = {
|
connectionLimit: 10,
|
||||||
host: appEnv.getMysqlHost(),
|
maxIdle: 10,
|
||||||
port: appEnv.getMysqlPort(),
|
idleTimeout: 60000,
|
||||||
user: appEnv.getMysqlUser(),
|
queueLimit: 0,
|
||||||
password: appEnv.getMysqlPassword(),
|
enableKeepAlive: true,
|
||||||
database: appEnv.getMysqlDatabase(),
|
keepAliveInitialDelay: 0,
|
||||||
waitForConnections: true,
|
ssl: {
|
||||||
connectionLimit: 10,
|
ca: fs.readFileSync(appEnv.getMysqlSslCaFile()),
|
||||||
maxIdle: 10,
|
key: fs.readFileSync(appEnv.getMysqlSslKeyFile()),
|
||||||
idleTimeout: 60000,
|
cert: fs.readFileSync(appEnv.getMysqlSslCertFile())
|
||||||
queueLimit: 0,
|
}
|
||||||
enableKeepAlive: true,
|
|
||||||
keepAliveInitialDelay: 0,
|
|
||||||
ssl: {
|
|
||||||
ca: fs.readFileSync(appEnv.getMysqlSslCaFile()),
|
|
||||||
key: fs.readFileSync(appEnv.getMysqlSslKeyFile()),
|
|
||||||
cert: fs.readFileSync(appEnv.getMysqlSslCertFile())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (typeof pool === 'undefined') {
|
||||||
|
pool = mysql.createPool(access)
|
||||||
|
}
|
||||||
|
|
||||||
|
return pool
|
||||||
}
|
}
|
||||||
|
|
||||||
export const pool = mysql.createPool(access)
|
export function getPromisePool(): pPool {
|
||||||
|
if (typeof promisePool === 'undefined') {
|
||||||
export const promisePool = pool.promise()
|
promisePool = getPool().promise()
|
||||||
|
}
|
||||||
|
return promisePool
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { RowDataPacket } from "mysql2";
|
import { RowDataPacket } from "mysql2";
|
||||||
import { promisePool } from "@/backend/db";
|
import { getPromisePool } from "@/backend/db";
|
||||||
|
|
||||||
export async function getPost(slug: string): Promise<string> {
|
export async function getPost(slug: string): Promise<string> {
|
||||||
try {
|
try {
|
||||||
const [rows, fields] = await promisePool.query<RowDataPacket[]>(
|
const [rows, fields] = await getPromisePool().query<RowDataPacket[]>(
|
||||||
'select content from post where slug = ?', [slug])
|
'select content from post where slug = ?', [slug])
|
||||||
return rows[0]['content']
|
return rows[0]['content']
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|||||||
@@ -1,8 +1,18 @@
|
|||||||
/** @type {import('next').NextConfig} */
|
/** @type {import('next').NextConfig} */
|
||||||
const nextConfig = {
|
const nextConfig = {
|
||||||
|
output: "standalone",
|
||||||
webpack: (config) => {
|
webpack: (config) => {
|
||||||
config.externals = [...config.externals, "jsdom"];
|
config.externals = [...config.externals, "jsdom"];
|
||||||
return config;
|
return config;
|
||||||
|
},
|
||||||
|
images: {
|
||||||
|
remotePatterns: [
|
||||||
|
{
|
||||||
|
protocol: "https",
|
||||||
|
hostname: "assets.suyono.me",
|
||||||
|
pathname: "/**"
|
||||||
|
},
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
25
package.json
25
package.json
@@ -9,24 +9,25 @@
|
|||||||
"lint": "next lint"
|
"lint": "next lint"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/dompurify": "^3.0.3",
|
"@types/dompurify": "^3.0.5",
|
||||||
"@types/jsdom": "^21.1.3",
|
"@types/jsdom": "^21.1.6",
|
||||||
"@types/node": "20.6.5",
|
"@types/node": "20.6.5",
|
||||||
"@types/react": "18.2.22",
|
"@types/react": "18.2.22",
|
||||||
"@types/react-dom": "18.2.7",
|
"@types/react-dom": "18.2.7",
|
||||||
"autoprefixer": "10.4.16",
|
"autoprefixer": "10.4.16",
|
||||||
"bright": "^0.8.4",
|
"bright": "^0.8.5",
|
||||||
"dompurify": "^3.0.6",
|
"dompurify": "^3.1.2",
|
||||||
"eslint": "8.50.0",
|
"eslint": "8.57.0",
|
||||||
"eslint-config-next": "13.5.2",
|
"eslint-config-next": "14.2.3",
|
||||||
"html-react-parser": "^4.2.2",
|
"html-react-parser": "^4.2.10",
|
||||||
"jsdom": "^22.1.0",
|
"jsdom": "^22.1.0",
|
||||||
"mysql2": "^3.6.1",
|
"mysql2": "^3.9.7",
|
||||||
"next": "13.5.2",
|
"next": "14.2.3",
|
||||||
"postcss": "8.4.30",
|
"postcss": "8.4.30",
|
||||||
"react": "18.2.0",
|
"react": "18.3.1",
|
||||||
"react-dom": "18.2.0",
|
"react-dom": "18.3.1",
|
||||||
"redis": "^4.6.10",
|
"redis": "^4.6.13",
|
||||||
|
"sharp": "^0.33.3",
|
||||||
"tailwindcss": "3.3.3",
|
"tailwindcss": "3.3.3",
|
||||||
"typescript": "5.2.2"
|
"typescript": "5.2.2"
|
||||||
},
|
},
|
||||||
|
|||||||
7
pm2.config.js
Normal file
7
pm2.config.js
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
module.exports = {
|
||||||
|
apps: [{
|
||||||
|
script: "server.js",
|
||||||
|
instances: 4,
|
||||||
|
exec_mode: "cluster"
|
||||||
|
}]
|
||||||
|
}
|
||||||
5152
pnpm-lock.yaml
generated
5152
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
Binary file not shown.
|
Before Width: | Height: | Size: 60 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 10 KiB |
@@ -12,10 +12,12 @@ const config: Config = {
|
|||||||
'gradient-radial': 'radial-gradient(var(--tw-gradient-stops))',
|
'gradient-radial': 'radial-gradient(var(--tw-gradient-stops))',
|
||||||
'gradient-conic':
|
'gradient-conic':
|
||||||
'conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))',
|
'conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))',
|
||||||
'blog-cover': "url('/assets/placeholder.webp')"
|
|
||||||
},
|
},
|
||||||
width: {
|
width: {
|
||||||
'224': '56rem',
|
'224': '56rem',
|
||||||
|
},
|
||||||
|
height: {
|
||||||
|
'192': '48rem',
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user