Skip to content
This repository has been archived by the owner on Jun 27, 2024. It is now read-only.

Commit

Permalink
refactor: move all accessories to root (#177)
Browse files Browse the repository at this point in the history
* refactor: move all accessories to root

* fix issues
  • Loading branch information
boywithkeyboard authored Aug 16, 2023
1 parent a920466 commit fcc380e
Show file tree
Hide file tree
Showing 7 changed files with 565 additions and 41 deletions.
12 changes: 11 additions & 1 deletion cheetah.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ export type AppConfig = {

oauth?: {
store: OAuthStore
cookie?: Parameters<ResponseContext['cookie']>[2]
cookie?: Parameters<ResponseContext['setCookie']>[2]
onSignIn?: (
c: Context,
data: OAuthSessionData,
Expand All @@ -82,6 +82,16 @@ export type AppConfig = {
* @since v1.3
*/
debug?: boolean

/**
* Desc
*
* @since v1.4
*/
versioning?: {
highest: 'v4'
lowest: `v${string}`
}
}

export class cheetah extends base<cheetah>() {
Expand Down
92 changes: 92 additions & 0 deletions jwt.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
// Copyright 2023 Samuel Kopp. All rights reserved. Apache-2.0 license.
import {
decode,
encode,
} from 'https://deno.land/std@0.198.0/encoding/base64.ts'
import {
create,
getNumericDate,
Payload as JwtPayload,
verify as _verify,
VerifyOptions,
} from 'https://deno.land/x/djwt@v2.8/mod.ts'

interface Payload {
iss?: string
sub?: string
aud?: string[] | string
/**
* A `Date` object or a `number` (in seconds) when the JWT will expire.
*/
exp?: Date | number
/**
* A `Date` object or a `number` (in seconds) until which the JWT will be invalid.
*/
nbf?: Date | number
iat?: number
jti?: string
[key: string]: unknown
}

export async function createKey() {
const key = await crypto.subtle.generateKey(
{ name: 'HMAC', hash: 'SHA-512' },
true,
['sign', 'verify'],
)

const exportedKey = await crypto.subtle.exportKey('raw', key)

return encode(exportedKey)
}

export function importKey(key: string) {
return crypto.subtle.importKey(
'raw',
decode(key).buffer,
{ name: 'HMAC', hash: 'SHA-512' },
true,
['sign', 'verify'],
)
}

/**
* Sign a payload.
*/
// deno-lint-ignore ban-types
export async function sign<T extends Record<string, unknown> = {}>(
payload: T & Payload,
secret: string | CryptoKey,
) {
const key = typeof secret === 'string' ? await importKey(secret) : secret

const { exp, nbf, ...rest } = payload

return await create({ alg: 'HS512', typ: 'JWT' }, {
...(exp && { exp: getNumericDate(exp) }),
...(nbf && { nbf: getNumericDate(nbf) }),
...rest,
}, key)
}

/**
* Verify the validity of a JWT.
*/
export async function verify<T extends Record<string, unknown> = Payload>(
token: string,
secret: string | CryptoKey,
options?: VerifyOptions,
) {
try {
const key = typeof secret === 'string' ? await importKey(secret) : secret

return await _verify(token, key, options) as JwtPayload & T
} catch (_err) {
return
}
}

export default {
sign,
verify,
}
146 changes: 146 additions & 0 deletions location_data.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
// Copyright 2023 Samuel Kopp. All rights reserved. Apache-2.0 license.
import { IncomingRequestCfProperties } from 'https://cdn.jsdelivr.net/npm/@cloudflare/workers-types@4.20230814.0/index.ts'
import { Context } from './context.ts'

type CloudflareRequest = Request & {
cf: IncomingRequestCfProperties
}

/**
* Inspect the geolocation data of the incoming request.
*
* You must either deploy your app to [Cloudflare Workers](https://developers.cloudflare.com/workers/runtime-apis/request/#incomingrequestcfproperties) or use [Cloudflare as a proxy](https://developers.cloudflare.com/support/network/configuring-ip-geolocation/) to use the `LocationData` API.
*/
export class LocationData {
#c: Context

constructor(c: Context) {
this.#c = c
}

/**
* The city the request originated from.
*
* @example 'Austin'
*/
get city() {
const city = (this.#c.req.raw as CloudflareRequest).cf?.city

if (!city && this.#c.__app.proxy === 'cloudflare') {
return this.#c.req.headers['cf-ipcity']
}

return city
}

/**
* If known, the ISO 3166-2 name for the first level region associated with the IP address of the incoming request.
*
* @example 'Texas'
*/
get region() {
return (this.#c.req.raw as CloudflareRequest).cf?.region
}

/**
* The [ISO 3166-1 Alpha 2](https://www.iso.org/iso-3166-country-codes.html) country code the request originated from.
*
* If you're using CLoudflare Workers and your worker is [configured to accept TOR connections](https://support.cloudflare.com/hc/en-us/articles/203306930-Understanding-Cloudflare-Tor-support-and-Onion-Routing), this may also be `T1`, indicating a request that originated over TOR.
*
* If Cloudflare is unable to determine where the request originated this property is omitted.
*
* @example 'GB'
*/
get country(): IncomingRequestCfProperties['country'] {
const country = (this.#c.req.raw as CloudflareRequest).cf?.country

if (!country && this.#c.__app.proxy === 'cloudflare') {
return this.#c.req
.headers['cf-ipcountry'] as IncomingRequestCfProperties['country']
}

return country
}

/**
* A two-letter code indicating the continent the request originated from.
*
* @example 'NA'
*/
get continent(): IncomingRequestCfProperties['continent'] {
const continent = (this.#c.req.raw as CloudflareRequest).cf?.continent

if (!continent && this.#c.__app.proxy === 'cloudflare') {
return this.#c.req
.headers['cf-ipcontinent'] as IncomingRequestCfProperties['continent']
}

return continent
}

/**
* If known, the ISO 3166-2 code for the first-level region associated with the IP address of the incoming request.
*
* @example 'TX'
*/
get regionCode(): IncomingRequestCfProperties['regionCode'] {
return (this.#c.req.raw as CloudflareRequest).cf?.regionCode
}

/**
* Latitude of the incoming request.
*
* @example '30.27130'
*/
get latitude(): IncomingRequestCfProperties['latitude'] {
const latitude = (this.#c.req.raw as CloudflareRequest).cf?.latitude

if (!latitude && this.#c.__app.proxy === 'cloudflare') {
return this.#c.req.headers['cf-iplatitude']
}

return latitude
}

/**
* Longitude of the incoming request.
*
* @example '-97.74260'
*/
get longitude(): IncomingRequestCfProperties['longitude'] {
const longitude = (this.#c.req.raw as CloudflareRequest).cf?.longitude

if (!longitude && this.#c.__app.proxy === 'cloudflare') {
return this.#c.req.headers['cf-iplongitude']
}

return longitude
}

/**
* Postal code of the incoming request.
*
* @example '78701'
*/
get postalCode(): IncomingRequestCfProperties['postalCode'] {
return (this.#c.req.raw as CloudflareRequest).cf?.postalCode
}

/**
* Timezone of the incoming request.
*
* @example 'America/Chicago'
*/
get timezone(): IncomingRequestCfProperties['timezone'] {
return (this.#c.req.raw as CloudflareRequest).cf?.timezone
}

/**
* The three-letter [IATA](https://en.wikipedia.org/wiki/IATA_airport_code) airport code of the data center that the request hit.
*
* @example 'DFW'
*/
get datacenter(): IncomingRequestCfProperties['colo'] {
return (this.#c.req.raw as CloudflareRequest).cf?.colo
}
}
90 changes: 50 additions & 40 deletions mod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,56 +6,66 @@ export { Context } from './context.ts'
export { Exception } from './exception.ts'
export { createExtension } from './extensions.ts'
export type { Extension } from './extensions.ts'
export { default as jwt } from './jwt.ts'
export { LocationData } from './location_data.ts'
export { otp } from './otp.ts'
export { sendMail } from './send_mail.ts'
export { Store } from './store.ts'

/* crypto ------------------------------------------------------------------- */

import { decode } from 'https://deno.land/std@0.198.0/encoding/base64.ts'
import { Context } from './context.ts'

export async function encrypt(key: string, message: string) {
const iv = crypto.getRandomValues(new Uint8Array(12)),
ivStr = Array.from(iv)
.map((byte) => String.fromCharCode(byte))
.join(''),
alg = { name: 'AES-GCM', iv },
cryptoKey = await crypto.subtle.importKey(
'raw',
decode(key).buffer,
alg,
true,
['encrypt', 'decrypt'],
),
cipherBuf = await crypto.subtle.encrypt(
alg,
cryptoKey,
new TextEncoder().encode(message),
),
cipherArr = Array.from(new Uint8Array(cipherBuf)),
cipherStr = cipherArr.map((byte) => String.fromCharCode(byte))
.join('')
export async function encrypt(c: Context, message: string) {
const key = (c.env('crypto_key') ?? c.env('CRYPTO_KEY')) as string

const iv = crypto.getRandomValues(new Uint8Array(12))
const ivStr = Array.from(iv)
.map((byte) => String.fromCharCode(byte))
.join('')
const alg = { name: 'AES-GCM', iv }
const cryptoKey = await crypto.subtle.importKey(
'raw',
decode(key).buffer,
alg,
true,
['encrypt', 'decrypt'],
)
const cipherBuf = await crypto.subtle.encrypt(
alg,
cryptoKey,
new TextEncoder().encode(message),
)
const cipherArr = Array.from(new Uint8Array(cipherBuf))
const cipherStr = cipherArr.map((byte) => String.fromCharCode(byte))
.join('')

return btoa(ivStr + cipherStr)
}

export async function decrypt(key: string, message: string) {
const iv = atob(message).slice(0, 12),
alg = {
name: 'AES-GCM',
iv: new Uint8Array(
Array.from(iv).map((char) => char.charCodeAt(0)),
),
},
cryptoKey = await crypto.subtle.importKey(
'raw',
decode(key).buffer,
alg,
true,
['encrypt', 'decrypt'],
),
cipherStr = atob(message).slice(12),
cipherBuf = new Uint8Array(
Array.from(cipherStr).map((char) => char.charCodeAt(0)),
export async function decrypt(c: Context, message: string) {
const key = (c.env('crypto_key') ?? c.env('CRYPTO_KEY')) as string

const iv = atob(message).slice(0, 12)
const alg = {
name: 'AES-GCM',
iv: new Uint8Array(
Array.from(iv).map((char) => char.charCodeAt(0)),
),
buf = await crypto.subtle.decrypt(alg, cryptoKey, cipherBuf)
}
const cryptoKey = await crypto.subtle.importKey(
'raw',
decode(key).buffer,
alg,
true,
['encrypt', 'decrypt'],
)
const cipherStr = atob(message).slice(12)
const cipherBuf = new Uint8Array(
Array.from(cipherStr).map((char) => char.charCodeAt(0)),
)
const buf = await crypto.subtle.decrypt(alg, cryptoKey, cipherBuf)

return new TextDecoder().decode(buf)
}
Loading

0 comments on commit fcc380e

Please sign in to comment.