diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..85e7c1d --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/.idea/ diff --git a/console/src/routes/+layout.svelte b/console/src/routes/+layout.svelte deleted file mode 100644 index 235b8ab..0000000 --- a/console/src/routes/+layout.svelte +++ /dev/null @@ -1,199 +0,0 @@ - - -{#if $auth?.token} - - -{:else} - -
-
-

JavascriptDB Console

-
- -
-
-
-
- -
- -
-
- -
- -
- -
-
- - - -
- -
-
- - -
-
-
-{/if} - diff --git a/console/src/services/jsdb.ts b/console/src/services/jsdb.ts index 9a52b74..72ce73a 100644 --- a/console/src/services/jsdb.ts +++ b/console/src/services/jsdb.ts @@ -1,3 +1,3 @@ import {initApp} from "@jsdb/sdk"; -export const {auth, db} = await initApp({serverUrl: 'http://localhost:3001', connector: 'HTTP'}) \ No newline at end of file +export const {auth, db} = await initApp({serverUrl: 'http://localhost:3001', connector: 'HTTP'}) diff --git a/sdk/package-lock.json b/sdk/package-lock.json index 8c1b624..bcb41d9 100644 --- a/sdk/package-lock.json +++ b/sdk/package-lock.json @@ -1,16 +1,15 @@ { "name": "@jsdb/sdk", - "version": "0.0.42", + "version": "0.0.45", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@jsdb/sdk", - "version": "0.0.42", + "version": "0.0.45", "license": "MIT", "dependencies": { - "isomorphic-ws": "^4.0.1", - "ws": "^8.5.0" + "@auth/core": "^0.9.0" }, "devDependencies": { "@size-limit/preset-small-lib": "^7.0.8", @@ -22,7 +21,7 @@ "typescript": "^3.9.10" }, "engines": { - "node": ">=18" + "node": ">=20" } }, "node_modules/@ampproject/remapping": { @@ -38,6 +37,27 @@ "node": ">=6.0.0" } }, + "node_modules/@auth/core": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@auth/core/-/core-0.9.0.tgz", + "integrity": "sha512-W2WO0WCBg1T3P8+yjQPzurTQhPv6ecBYfJ2oE3uvXPAX5ZLWAMSjKFAIa9oLZy5pwrB+YehJZPnlIxVilhrVcg==", + "dependencies": { + "@panva/hkdf": "^1.0.4", + "cookie": "0.5.0", + "jose": "^4.11.1", + "oauth4webapi": "^2.0.6", + "preact": "10.11.3", + "preact-render-to-string": "5.2.3" + }, + "peerDependencies": { + "nodemailer": "^6.8.0" + }, + "peerDependenciesMeta": { + "nodemailer": { + "optional": true + } + } + }, "node_modules/@babel/code-frame": { "version": "7.18.6", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", @@ -2247,6 +2267,14 @@ "node": ">= 8" } }, + "node_modules/@panva/hkdf": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@panva/hkdf/-/hkdf-1.1.1.tgz", + "integrity": "sha512-dhPeilub1NuIG0X5Kvhh9lH4iW3ZsHlnzwgwbOlgwQ2wG1IqFzsgHqmKPk3WzsdWAeaxKJxgM0+W433RmN45GA==", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, "node_modules/@rollup/plugin-babel": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-5.3.1.tgz", @@ -3923,6 +3951,14 @@ "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", "dev": true }, + "node_modules/cookie": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", + "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/copy-descriptor": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", @@ -6901,14 +6937,6 @@ "node": ">=0.10.0" } }, - "node_modules/isomorphic-ws": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/isomorphic-ws/-/isomorphic-ws-4.0.1.tgz", - "integrity": "sha512-BhBvN2MBpWTaSHdWRb/bwdZJ1WaehQ2L1KngkCkfLUGF0mAWAT1sQUQacEmQ0jXkFw/czDXPNQSL5u2/Krsz1w==", - "peerDependencies": { - "ws": "*" - } - }, "node_modules/isstream": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", @@ -7852,6 +7880,14 @@ "node": ">= 8.3" } }, + "node_modules/jose": { + "version": "4.14.4", + "resolved": "https://registry.npmjs.org/jose/-/jose-4.14.4.tgz", + "integrity": "sha512-j8GhLiKmUAh+dsFXlX1aJCbt5KMibuKb+d7j1JaOJG6s2UjX1PQlW+OKB/sD4a/5ZYF4RcmYmLSndOoU3Lt/3g==", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, "node_modules/jpjs": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/jpjs/-/jpjs-1.2.1.tgz", @@ -8701,6 +8737,14 @@ "node": "*" } }, + "node_modules/oauth4webapi": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/oauth4webapi/-/oauth4webapi-2.3.0.tgz", + "integrity": "sha512-JGkb5doGrwzVDuHwgrR4nHJayzN4h59VCed6EW8Tql6iHDfZIabCJvg6wtbn5q6pyB2hZruI3b77Nudvq7NmvA==", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -9227,6 +9271,31 @@ "node": ">=0.10.0" } }, + "node_modules/preact": { + "version": "10.11.3", + "resolved": "https://registry.npmjs.org/preact/-/preact-10.11.3.tgz", + "integrity": "sha512-eY93IVpod/zG3uMF22Unl8h9KkrcKIRs2EGar8hwLZZDU1lkjph303V9HZBwufh2s736U6VXuhD109LYqPoffg==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/preact" + } + }, + "node_modules/preact-render-to-string": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/preact-render-to-string/-/preact-render-to-string-5.2.3.tgz", + "integrity": "sha512-aPDxUn5o3GhWdtJtW0svRC2SS/l8D9MAgo2+AWml+BhDImb27ALf04Q2d+AHqUUOc6RdSXFIBVa2gxzgMKgtZA==", + "dependencies": { + "pretty-format": "^3.8.0" + }, + "peerDependencies": { + "preact": ">=10" + } + }, + "node_modules/preact-render-to-string/node_modules/pretty-format": { + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-3.8.0.tgz", + "integrity": "sha512-WuxUnVtlWL1OfZFQFuqvnvs6MiAGk9UNsBostyBOB0Is9wb5uRESevA6rnl/rkksXaGX3GzZhPup5d6Vp1nFew==" + }, "node_modules/prelude-ls": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", @@ -12236,26 +12305,6 @@ "mkdirp": "bin/cmd.js" } }, - "node_modules/ws": { - "version": "8.11.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz", - "integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==", - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": "^5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, "node_modules/xml-name-validator": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz", @@ -12345,6 +12394,19 @@ "@jridgewell/trace-mapping": "^0.3.9" } }, + "@auth/core": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@auth/core/-/core-0.9.0.tgz", + "integrity": "sha512-W2WO0WCBg1T3P8+yjQPzurTQhPv6ecBYfJ2oE3uvXPAX5ZLWAMSjKFAIa9oLZy5pwrB+YehJZPnlIxVilhrVcg==", + "requires": { + "@panva/hkdf": "^1.0.4", + "cookie": "0.5.0", + "jose": "^4.11.1", + "oauth4webapi": "^2.0.6", + "preact": "10.11.3", + "preact-render-to-string": "5.2.3" + } + }, "@babel/code-frame": { "version": "7.18.6", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", @@ -13951,6 +14013,11 @@ "fastq": "^1.6.0" } }, + "@panva/hkdf": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@panva/hkdf/-/hkdf-1.1.1.tgz", + "integrity": "sha512-dhPeilub1NuIG0X5Kvhh9lH4iW3ZsHlnzwgwbOlgwQ2wG1IqFzsgHqmKPk3WzsdWAeaxKJxgM0+W433RmN45GA==" + }, "@rollup/plugin-babel": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-5.3.1.tgz", @@ -15240,6 +15307,11 @@ "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", "dev": true }, + "cookie": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", + "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==" + }, "copy-descriptor": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", @@ -17386,12 +17458,6 @@ "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", "dev": true }, - "isomorphic-ws": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/isomorphic-ws/-/isomorphic-ws-4.0.1.tgz", - "integrity": "sha512-BhBvN2MBpWTaSHdWRb/bwdZJ1WaehQ2L1KngkCkfLUGF0mAWAT1sQUQacEmQ0jXkFw/czDXPNQSL5u2/Krsz1w==", - "requires": {} - }, "isstream": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", @@ -18169,6 +18235,11 @@ "supports-color": "^7.0.0" } }, + "jose": { + "version": "4.14.4", + "resolved": "https://registry.npmjs.org/jose/-/jose-4.14.4.tgz", + "integrity": "sha512-j8GhLiKmUAh+dsFXlX1aJCbt5KMibuKb+d7j1JaOJG6s2UjX1PQlW+OKB/sD4a/5ZYF4RcmYmLSndOoU3Lt/3g==" + }, "jpjs": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/jpjs/-/jpjs-1.2.1.tgz", @@ -18852,6 +18923,11 @@ "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", "dev": true }, + "oauth4webapi": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/oauth4webapi/-/oauth4webapi-2.3.0.tgz", + "integrity": "sha512-JGkb5doGrwzVDuHwgrR4nHJayzN4h59VCed6EW8Tql6iHDfZIabCJvg6wtbn5q6pyB2hZruI3b77Nudvq7NmvA==" + }, "object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -19240,6 +19316,26 @@ "integrity": "sha512-xTgYBc3fuo7Yt7JbiuFxSYGToMoz8fLoE6TC9Wx1P/u+LfeThMOAqmuyECnlBaaJb+u1m9hHiXUEtwW4OzfUJg==", "dev": true }, + "preact": { + "version": "10.11.3", + "resolved": "https://registry.npmjs.org/preact/-/preact-10.11.3.tgz", + "integrity": "sha512-eY93IVpod/zG3uMF22Unl8h9KkrcKIRs2EGar8hwLZZDU1lkjph303V9HZBwufh2s736U6VXuhD109LYqPoffg==" + }, + "preact-render-to-string": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/preact-render-to-string/-/preact-render-to-string-5.2.3.tgz", + "integrity": "sha512-aPDxUn5o3GhWdtJtW0svRC2SS/l8D9MAgo2+AWml+BhDImb27ALf04Q2d+AHqUUOc6RdSXFIBVa2gxzgMKgtZA==", + "requires": { + "pretty-format": "^3.8.0" + }, + "dependencies": { + "pretty-format": { + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-3.8.0.tgz", + "integrity": "sha512-WuxUnVtlWL1OfZFQFuqvnvs6MiAGk9UNsBostyBOB0Is9wb5uRESevA6rnl/rkksXaGX3GzZhPup5d6Vp1nFew==" + } + } + }, "prelude-ls": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", @@ -21626,12 +21722,6 @@ "typedarray-to-buffer": "^3.1.5" } }, - "ws": { - "version": "8.11.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz", - "integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==", - "requires": {} - }, "xml-name-validator": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz", diff --git a/sdk/package.json b/sdk/package.json index b05b8d6..38fd1d5 100644 --- a/sdk/package.json +++ b/sdk/package.json @@ -54,7 +54,9 @@ "tslib": "^2.4.0", "typescript": "^3.9.10" }, - "dependencies": {}, + "dependencies": { + "@auth/core": "^0.9.0" + }, "description": "Install ```shell npm i @jsdb/sdk ```", "directories": { "test": "test" diff --git a/sdk/src/index.ts b/sdk/src/index.ts index 18f2875..84abe76 100644 --- a/sdk/src/index.ts +++ b/sdk/src/index.ts @@ -1,8 +1,10 @@ +import {getSignInPopup} from './providerSignin' type document = { id: string, [key: string]: any } type fn = (v: any) => any const regexpIsoDate = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*))(?:Z|(\+|-)([\d|:]*))?$/; +const SECONDS_TIMEOUT_SIGNIN_WINDOW = 60; export async function initApp(config: { serverUrl?: string, apiKey?: string, connector: 'HTTP' | 'LOCAL' | 'WS', opHandlers?: any } = {connector: 'HTTP'}) { config = {...{connector: 'HTTP'}, ...config}; let baseUrl = ''; @@ -155,25 +157,74 @@ export async function initApp(config: { serverUrl?: string, apiKey?: string, con } } - signOut = () => { - delete localStorage.token; - delete localStorage.userId; - this.set({}); - }; + signOut = () => { + delete localStorage.token; + delete localStorage.userId; + this.set({}) + } + async signInWithProvider(provider: string) { + return new Promise((resolve, reject) => { + const width = 450, height = 550, left = (screen.width - width) / 2, top = (screen.height - height) / 2; + let params = `width=${width}, height=${height}, top=${top}, left=${left}, titlebar=no, location=yes` + let timeout = setTimeout(() => { + loginWindow.close(); + reject({message:`signInWith timeout exceeded`}) + }, SECONDS_TIMEOUT_SIGNIN_WINDOW*1000) + let loginWindow: any; + const url = new URL('/', baseUrl); + const uniqueWindowId = `authorizationJavascriptDatabase`; + const handleMessage = (e: MessageEvent)=> { + console.log('Message: ', e.data) + const {token, user} = e.data; + clearTimeout(timeout); + loginWindow.close(); + window.removeEventListener('message', handleMessage) + resolve({token, user}); + } + window.addEventListener("message", handleMessage , false); + loginWindow = window.open(url.toString(), uniqueWindowId, params) + loginWindow.document.write(getSignInPopup(baseUrl, provider)); + }) + } + async defaultSignIn() { + return new Promise((resolve, reject) => { + const width = 450, height = 550, left = (screen.width - width) / 2, top = (screen.height - height) / 2; + let params = `width=${width}, height=${height}, top=${top}, left=${left}, titlebar=no, location=yes` + let timeout = setTimeout(() => { + loginWindow.close(); + reject({message:`signInWith timeout exceeded`}); + }, SECONDS_TIMEOUT_SIGNIN_WINDOW*1000) + let loginWindow: any; + const url = new URL('/auth/signin', baseUrl); + const uniqueWindowId = `authorizationJavascriptDatabase`; + const handleMessage = (e: MessageEvent)=> { + const {token, user} = JSON.parse(e.data); + console.log('Message: ', e.data) + clearTimeout(timeout); + loginWindow.close(); + window.removeEventListener('message', handleMessage) + resolve({token, user}); + } + window.addEventListener("message", handleMessage , false); + loginWindow = window.open(url.toString(), uniqueWindowId, params) + }) + } signIn = async (credentials: { email: string, password: string }) => { - try { - const {token, userId} = await request('/auth/signin', {...credentials}); - this.set({token, userId}); - if (typeof process !== 'object') { - localStorage.token = this.value.token; - localStorage.userId = this.value.userId; + try { + location.href = baseUrl + '/auth/signin'; + console.log(credentials) + // this.set({token, userId}) + // if (typeof process !== 'object') { + // localStorage.token = this.value.token; + // localStorage.userId = this.value.userId; + // } + // return true; + } catch (e) { + console.error(e); + throw new Error(`Error logging in, verify email and password`); } - return true; - } catch (e) { - throw new Error(`Error logging in, verify email and password`); - } - }; + } createAccount = async (credentials: { email: string, password: string }) => { try { diff --git a/sdk/src/providerSignin.ts b/sdk/src/providerSignin.ts new file mode 100644 index 0000000..cba5b2d --- /dev/null +++ b/sdk/src/providerSignin.ts @@ -0,0 +1,38 @@ +export function getSignInPopup(baseUrl: string, provider: string) { + return ` + + + + + + ` +} diff --git a/server/http/auth.js b/server/http/auth.js index e69de29..cb0eaa5 100644 --- a/server/http/auth.js +++ b/server/http/auth.js @@ -0,0 +1,133 @@ +import {Auth} from '@auth/core'; +import GitHub from '@auth/core/providers/github'; +import {readReadableStream} from '../utils.js'; +import {decode} from '@auth/core/jwt'; +import {sdkDb} from './sdk.js'; +import jsdbAdapter from './jsdbAdapter.js'; + +// Request https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API needed by Auth.js +export async function AuthModule(request) { + const optionsEnvVar = { + // Configure one or more authentication providers + providers: [ + GitHub({ + clientId: '8a9219d06d63a95bf1af', + clientSecret: '746ce3dd62cfbd400289e7647063ecc907ffab17', + }), + ], + trustHost: true, + secret: process.env.JWT_SECRET, + cookies: { + csrfToken: { + name: 'next-auth.csrf-token', + options: { + httpOnly: true, + sameSite: 'none', + path: '/', + secure: true + } + }, + pkceCodeVerifier: { + name: 'next-auth.pkce.code_verifier', + options: { + httpOnly: true, + sameSite: 'none', + path: '/', + secure: true + } + } + }, + callbacks: { + async signIn(args) { + // async signIn({user, account, profile, email, credentials}) { + // console.log(`signIn callback`) + return true + }, + // async redirect({ url, baseUrl }) { + async redirect(args) { + //console.log(`redirect callback: ${JSON.stringify(args)}`) + return null + //return baseUrl + }, + // async session({session, user, token}) { + async session(args) { + //console.log(`session callback: ${JSON.stringify(args)}`) + return args.session + }, + // async jwt({token, user, account, profile, isNewUser}) { + async jwt(args) { + console.log(`jwt callback: ${JSON.stringify(args)}`) + return args.token; + } + }, + adapter: jsdbAdapter(sdkDb) + } + let headers = new Headers(); + const url = new URL(request.url) + if (['auth', 'db', 'functions'].includes(url.pathname.split('/')[1])) { + headers.set('Access-Control-Allow-Methods', 'OPTIONS, POST, GET') + headers.set('Access-Control-Allow-Headers', '*') + headers.set('Access-Control-Allow-Credentials', true) + if (request.headers.get('origin')) { + headers.set('Access-Control-Allow-Origin', request.headers.get('origin')) + } + if (request.method === 'OPTIONS') { + return new Response(null, {headers, status: 204}); + } + if(url.pathname.includes('auth/signin/')) { + const body = await readReadableStream(request.clone().body) + const searchParams = new URLSearchParams(body); + if(searchParams?.get('customProviderSignin') === 'true') { + const pathname = (new URL(request.url)).pathname + const clone = new Request(new URL(pathname, process.env.SERVER_URL) , request) + request = clone + console.log('here') + } + } + const auth = await Auth(request, optionsEnvVar); + headers.set('access-control-expose-headers', 'set-cookie') + // merge both auth and added headers + for (const headerName of auth.headers.keys()) { + const header = auth.headers.get(headerName); + headers.set(headerName, header) + if (headerName === 'set-cookie') { + console.log(header) + } + } + for (const headerName of headers.keys()) { + const header = headers.get(headerName); + auth.headers.set(headerName, header) + } + if(url.pathname.includes('auth/callback') ) { + headers.set('Content-type', 'text/html') + const codedSessionToken = headers.get('set-cookie') + ?.split(';') + ?.map(dirtyCookie => dirtyCookie.split(',')) + ?.flat() + ?.filter(keyValue => keyValue.includes('next-auth.session-token'))?.[0] + ?.split('=')?.[1]; + const decodedSessionToken = await decode({ + token: codedSessionToken, + secret: process.env.JWT_SECRET, + }) + + const resp = ` + +` + return new Response(resp, {headers, status: 200}); + } + // comes from sdk and all redirections needs to be triggered manually from the sdk + let origin = request.headers.get('origin') + if(origin) origin = new URL(origin); + if (auth.status === 302 && origin && origin.href !== process.env.SERVER_URL) { + // custom provider signin + return new Response(auth.headers.get('location'), {headers, status: 200}); + } else { + return auth + } + } + return null +} + diff --git a/server/http/base.js b/server/http/base.js index 93f69f2..eb6ab0a 100644 --- a/server/http/base.js +++ b/server/http/base.js @@ -19,4 +19,4 @@ export async function route(path, body, user, skipSecurityRules, skipTrigger) { } else if (module === 'storage') { return routeStorage(operation, body); } -} \ No newline at end of file +} diff --git a/server/http/jsdbAdapter.js b/server/http/jsdbAdapter.js new file mode 100644 index 0000000..7f8d15e --- /dev/null +++ b/server/http/jsdbAdapter.js @@ -0,0 +1,49 @@ +export default function MyAdapter(dbSdk) { + return { + async createUser(user) { + console.log('here') + await dbSdk.set('test', user) + // dbSdk.set('hello', {test:true}) + return + }, + async getUser(id) { + return + }, + async getUserByEmail(email) { + return + }, + async getUserByAccount({ providerAccountId, provider }) { + return + }, + async updateUser(user) { + return + }, + async deleteUser(userId) { + return + }, + async linkAccount(account) { + return + }, + async unlinkAccount({ providerAccountId, provider }) { + return + }, + async createSession({ sessionToken, userId, expires }) { + return + }, + async getSessionAndUser(sessionToken) { + return + }, + async updateSession({ sessionToken }) { + return + }, + async deleteSession(sessionToken) { + return + }, + async createVerificationToken({ identifier, expires, token }) { + return + }, + async useVerificationToken({ identifier, token }) { + return + }, + } +} diff --git a/server/http/sdk.js b/server/http/sdk.js new file mode 100644 index 0000000..b3e8a41 --- /dev/null +++ b/server/http/sdk.js @@ -0,0 +1,6 @@ +import {initApp} from "@jsdb/sdk"; +export let sdkApp, sdkDb; +export async function initSdk() { + sdkApp = await initApp({serverUrl: process.env.SERVER_URL, connector: 'HTTP'}) + sdkDb = sdkApp.db; +} diff --git a/server/package.json b/server/package.json index c75a36f..9db5425 100644 --- a/server/package.json +++ b/server/package.json @@ -11,6 +11,7 @@ "author": "jpcapdevila", "license": "SSPL", "dependencies": { + "@auth/core": "^0.9.0", "@jsdb/sdk": "file:../sdk", "acorn": "^8.7.1", "aws4fetch": "^1.0.17", diff --git a/server/runtimes/node/index.js b/server/runtimes/node/index.js index af3da86..078f0d7 100644 --- a/server/runtimes/node/index.js +++ b/server/runtimes/node/index.js @@ -1,16 +1,69 @@ import * as http from 'http'; import {WebSocketServer} from 'ws'; import {route} from '../../http/base.js'; -import {parseData, readStreamToPromise} from '../../utils.js'; +import {parseData, readReadableStream, readStreamToPromise} from '../../utils.js'; import jwt from 'jsonwebtoken'; import {getEventStore} from '../../ws/ws.js'; +import {AuthModule} from '../../http/auth.js'; +import {initSdk} from '../../http/sdk.js'; + const wsServer = new WebSocketServer({noServer: true}); -const hostname = '0.0.0.0'; +const hostname = 'localhost' const port = process.env.PORT || 3001; +async function getBody(request) { + return new Promise((resolve) => { + const bodyParts = []; + let body; + request.on('data', (chunk) => { + bodyParts.push(chunk); + }).on('end', () => { + body = Buffer.concat(bodyParts).toString(); + resolve(body) + }); + }); +} +async function convertIncomingMessageToRequest(req){ + const headers = new Headers(); + for (var key in req.headers) { + if (req.headers[key]) headers.append(key, req.headers[key]); + } + const body = req.method === 'POST' ? await getBody(req) : null; + // TODO remove hardcoded http + const baseUrl = process.env.SERVER_URL + console.log((new URL(req.url, baseUrl)).pathname) + const reqObj = { + ...req, + body, + headers, + } + let request = new Request(new URL(req.url, baseUrl), reqObj) + return request +} +async function convertResponseToServerResponse(response, serverResponse) { + const headers = {}; + for (const headerName of response.headers.keys()) { + const header = response.headers.get(headerName); + headers[headerName] = header + } + serverResponse.writeHead(response.status, headers) + if(response.body) { + const body = await readReadableStream(response.body) + serverResponse.write(body) + } +} + const server = http.createServer(async (req, res) => { + const request = await convertIncomingMessageToRequest(req) + const authResp = await AuthModule(request); + if(authResp) { + await convertResponseToServerResponse(authResp, res); + // Todo, remove this return and see if the user is auth or not + return res.end() + } + if (req.method === 'POST') { const bodyString = await readStreamToPromise(req); const body = parseData(bodyString); @@ -25,10 +78,13 @@ const server = http.createServer(async (req, res) => { res.end(JSON.stringify(result ?? null)); } } + res.end(); }); -server.listen(port, hostname, () => { +server.listen(port, hostname, async () => { + await initSdk() console.log(`Server running at http://${hostname}:${port}/`); + }); server.on('upgrade', (request, socket, head) => { @@ -78,4 +134,4 @@ wsServer.on('connection', socket => { } } }); -}); \ No newline at end of file +}); diff --git a/server/utils.js b/server/utils.js index 34d7ca7..dd369ab 100644 --- a/server/utils.js +++ b/server/utils.js @@ -1,3 +1,5 @@ +import { Buffer } from 'node:buffer'; + export function readStreamToPromise(stream) { return new Promise((resolve, reject) => { const chunks = []; @@ -17,4 +19,11 @@ export function parseData(dataString) { return value; } }); -} \ No newline at end of file +} +export async function readReadableStream(readableStream) { + const chunks = []; + for await (const chunk of readableStream) { + chunks.push(Buffer.from(chunk)); + } + return Buffer.concat(chunks).toString("utf-8"); +}