Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions .github/workflows/CI.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,27 @@ jobs:
- name: Run profile
run: pnpm profile:deno

profile-deno-graph:
runs-on: ubuntu-latest

steps:
- name: setup repo
uses: actions/checkout@v3

- name: setup pnpm
run: npm install -g pnpm

- name: setup packages
run: pnpm install

- name: setup Deno
uses: denoland/setup-deno@v2
with:
deno-version: v2.4.x

- name: Run profile
run: pnpm profile:deno:graph

profile-bun:
runs-on: ubuntu-latest

Expand Down
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@

.vscode
coverage/
node_modules
node_modules
dist/
68 changes: 68 additions & 0 deletions deno.lock

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

25 changes: 12 additions & 13 deletions examples/simpleAuth/app.ts → examples/auth/app.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,26 @@
import * as Peko from "../../mod.ts"; // "https://deno.land/x/peko/mod.ts"

const html = String;
const crypto = new Peko.Krypto("SUPER_SECRET_KEY_123"); // TODO: replace with env var
export const html = String;
export const krypto = new Peko.Krypto("SUPER_SECRET_KEY_123"); // TODO: replace with env var

// TODO: replace with db / auth provider query
const user = {
export const user = {
username: "test-user",
password: await crypto.hash("test-password"),
password: await krypto.hash("test-password"),
};

type JWTPayload = {
export type JWTPayload = {
iat: number,
exp: number,
data: { user: string },
data: { username: string },
}

const validateUser = async (username: string, password: string) => {
export const validateUser = async (username: string, password: string) => {
return (
username &&
password &&
username === user.username &&
(await crypto.hash(password)) === user.password
(await krypto.hash(password)) === user.password
);
};

Expand All @@ -41,9 +41,9 @@ class SimpleAuthRouter extends Peko.HttpRouterFactory({
const jwtPayload: JWTPayload = {
iat: Date.now(),
exp: exp.valueOf(),
data: { user: username },
data: { username: username },
}
const jwt = await crypto.sign(jwtPayload);
const jwt = await krypto.sign(jwtPayload);

return new Response(jwt, {
status: 201,
Expand All @@ -55,11 +55,10 @@ class SimpleAuthRouter extends Peko.HttpRouterFactory({

verify = this.GET(
"/verify",
[Peko.auth<JWTPayload>(crypto)],
[Peko.auth<JWTPayload>(krypto)],
(ctx) => {
console.log(ctx.state.auth)
return ctx.state.auth
? new Response(`You are authenticated as ${ctx.state.auth.data.user}`)
? new Response(`You are authenticated as ${ctx.state.auth.data.username}`)
: new Response("You are not authenticated", {
status: 401
})
Expand Down
200 changes: 200 additions & 0 deletions examples/graphql/app.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
import { ValidationError } from "../../lib/core/utils/ValidationError.ts";
import { Schema } from "../../lib/graph/utils/Schema.ts";
import { GraphRouterFactory, ModelFactory, FieldFactory, auth, HttpRouterFactory, ssr, parseQuery } from "../../mod.ts";
import { JWTPayload, krypto, validateUser, html } from "../auth/app.ts";

class User extends ModelFactory({
username: FieldFactory(String),
password: FieldFactory(String),
}) {}

class LoginData extends ModelFactory({
...User.schema,
jwt: FieldFactory(String)
}) {}

class MyGraphRouter extends GraphRouterFactory({
middleware: [
auth<JWTPayload>(krypto),
parseQuery,
]
}) {
login = this.mutation("login", {
type: LoginData,
args: User,
resolver: async (_ctx, args) => {
const { username, password } = args;

if (!(await validateUser(username.valueOf(), password.valueOf()))) {
throw new ValidationError("Bad credentials");
}

const exp = new Date();
exp.setMonth(exp.getMonth() + 1);
const jwtPayload: JWTPayload = {
iat: Date.now(),
exp: exp.valueOf(),
data: { username: username.valueOf() },
}
const jwt = await krypto.sign(jwtPayload);

return new LoginData({
username,
password: "redacted",
jwt
});
}
});

verify = this.query("verify", {
type: LoginData,
resolver: (ctx) => {
if (!ctx.state.auth) throw new ValidationError("No auth credentials.")
return new LoginData({
username: ctx.state.auth.data.username,
password: "redacted",
jwt: ctx.request.headers.get("Authorization")?.slice(7) || ""
})
}
});
}

class MyHTTPRouter extends HttpRouterFactory({
middleware: [
// log(console.log)
]
}) {
graphRouter = new MyGraphRouter();

schema = this.GET("/graphql", () => new Response(new Schema(this.graphRouter.routes).toString()));

query = this.POST("/graphql", (ctx) => this.graphRouter.handle(ctx.request));

authPage = this.GET(
"/",
ssr(
() => html`<!DOCTYPE html>
<html lang="en">
<head>
<title>Peko auth example</title>
<style>
html,
body {
height: 100%;
width: 100%;
margin: 0;
background-color: steelblue;
}
.auth-box {
width: 300px;
margin: 10rem auto;
padding: 1rem;
border: 1px solid black;
}
</style>
</head>
<body>
<div class="auth-box">
<input id="email" type="email" value="test-user" />
<input id="password" type="password" value="test-password" />
<button id="login">Login</button>
<button onclick="verify()">Test Auth</button>
<p>Status: <span id="status"></span></p>
<p>
Response:
<span id="response" style="word-wrap: break-word"></span>
</p>
</div>

<script>
const email = document.querySelector("#email");
const password = document.querySelector("#password");
const loginBtn = document.querySelector("#login");
const status = document.querySelector("#status");
const responseText = document.querySelector("#response");
let jwt;

async function login() {
const response = await fetch("/graphql", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
query: String("mutation Login($username: String!, $password: String!) { " +
"login(username: $username, password: $password) { " +
"username " +
"password " +
"jwt " +
"} " +
"}"),
variables: {
username: email.value,
password: password.value,
}
}),
});

const json = await response.json();
console.log(json);
jwt = json.data.login.jwt;
responseText.innerText = jwt;

updateStatus(response.status);
updateButton();
}

function logout() {
jwt = "";
responseText.innerText = jwt;

updateStatus();
updateButton();
}

async function verify() {
const response = await fetch("/graphql", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": "Bearer " + jwt
},
body: JSON.stringify({
query: String("query Verify { " +
"verify { " +
"username " +
"password " +
"jwt " +
"} " +
"}")
}),
});

updateStatus(response.status);
responseText.innerText = await response.text();
}

function updateStatus(statusCode = "") {
status.innerText = statusCode;
if (statusCode !== 200 && statusCode !== 201) {
status.style.color = "red";
} else {
status.style.color = "limegreen";
}
}

function updateButton() {
loginBtn.textContent = jwt ? "Logout" : "Login";
loginBtn.onclick = jwt ? logout : login;
}

updateButton();
</script>
</body>
</html>
`
)
);
}

export default new MyHTTPRouter();
Loading
Loading