From a069b90acfbbb5c40c894059558823263b440edf Mon Sep 17 00:00:00 2001 From: nkBrew Date: Wed, 11 Oct 2023 17:26:46 -0600 Subject: [PATCH 01/19] Implemented basic, hawk, digest auths, ip, and cookies --- packages/firecamp-echo-server/package.json | 4 + packages/firecamp-echo-server/src/main.ts | 5 + .../src/rest/rest.controller.ts | 138 +++++++++++++++--- 3 files changed, 129 insertions(+), 18 deletions(-) diff --git a/packages/firecamp-echo-server/package.json b/packages/firecamp-echo-server/package.json index 77b1a3f80..c433a9efc 100644 --- a/packages/firecamp-echo-server/package.json +++ b/packages/firecamp-echo-server/package.json @@ -27,6 +27,8 @@ "@nestjs/websockets": "9.0.1", "class-transformer": "0.5.1", "class-validator": "0.14.0", + "cookie-parser": "^1.4.6", + "hawk": "^9.0.1", "raw-body": "^2.5.2", "reflect-metadata": "0.1.13", "rimraf": "3.0.2", @@ -40,7 +42,9 @@ "@nestjs/cli": "9.0.0", "@nestjs/schematics": "9.0.1", "@nestjs/testing": "9.0.1", + "@types/cookie-parser": "^1.4.4", "@types/express": "4.17.13", + "@types/hawk": "^9.0.4", "@types/node": "18.0.3", "@types/supertest": "2.0.12", "@types/ws": "8.5.3", diff --git a/packages/firecamp-echo-server/src/main.ts b/packages/firecamp-echo-server/src/main.ts index 42193859a..7697e0657 100644 --- a/packages/firecamp-echo-server/src/main.ts +++ b/packages/firecamp-echo-server/src/main.ts @@ -2,6 +2,7 @@ import { NestFactory } from '@nestjs/core'; import { WsAdapter, } from '@nestjs/platform-ws'; // import { IoAdapter } from '@nestjs/platform-socket.io'; import { AppModule } from './app.module'; +import * as cookieParser from 'cookie-parser' async function bootstrap() { const app = await NestFactory.create(AppModule); @@ -9,7 +10,11 @@ async function bootstrap() { app.useWebSocketAdapter(new WsAdapter(app)); // app.useWebSocketAdapter(new IoAdapter(app)); + + app.use(cookieParser()) + await app.listen(3000); console.log(`Application is running on: ${await app.getUrl()}`); + app.getUrl().then(url => console.log(url)) } bootstrap(); diff --git a/packages/firecamp-echo-server/src/rest/rest.controller.ts b/packages/firecamp-echo-server/src/rest/rest.controller.ts index 08dfb4c75..0ba2eec7f 100644 --- a/packages/firecamp-echo-server/src/rest/rest.controller.ts +++ b/packages/firecamp-echo-server/src/rest/rest.controller.ts @@ -1,6 +1,12 @@ import * as rawBody from 'raw-body'; -import { BadRequestException, Body, Controller, Get, Headers, Param, Post, Put, Query, Req, Response, HttpStatus, HttpCode, Patch, Delete } from '@nestjs/common'; - +import { BadRequestException, Body, Controller, Get, Headers, Param, Post, Put, Query, Req, Response, HttpStatus, HttpCode, Patch, Delete, Head, Ip } from '@nestjs/common'; +import * as crypto from 'crypto' +import { response } from 'express'; +import * as Hawk from 'hawk' +import { Credentials } from 'hawk/lib/server'; +const username = 'firecamp' +const password = 'password' +const nonce = crypto.randomBytes(16).toString('base64') @Controller('') export class RestController { @@ -135,23 +141,99 @@ export class RestController { // Basic Auth @Get('basic-auth') - basicAuth() { - return 'yet to implement' - // return { authenticated: true } + basicAuth(@Query() queryParams, @Headers() headers) { + if (!('authorization' in headers )){ + return {authenticated:false} + } + const split = headers.authorization.split(" ") + const type = split[0] + if (type != 'Basic'){ + return {authenticated:false} + } + const decoded = Buffer.from(split[1], 'base64').toString() + const decodedSplit = decoded.split(':') + if (decodedSplit.length != 2){ + return {authenticated:false} + } + const username = decodedSplit[0] + const password = decodedSplit[1] + console.log(username, password) + + return { authenticated: true } } // Digest Auth @Get('digest-auth') - digestAuth() { - return 'yet to implement' - // return { authenticated: true } + digestAuth(@Req() req,@Headers() headers, @Response() res) { + const realm = 'Users' + + if (!('authorization' in headers)){ + res.set({'www-authenticate':`Digest realm="${realm}", nonce="${nonce}"`}).status(401); + return res.json('Unauthorized') + } + + const split = headers.authorization.replace(',','').split(" ") + const type = split[0] + if (type != 'Digest'){ + return res.json('Unauthorized') + } + const {authorization} = headers + + const authArgs = authorization.slice(7).split(', ') + const argsMap = {} + console.log(authArgs) + authArgs.forEach(arg => { + const split = arg.replaceAll('"','').replace('=', '-:-').split('-:-') + argsMap[split[0]] = split[1] + }) + console.log(argsMap) + + if (!(argsMap['username'] && argsMap['nonce'] && argsMap['realm'] && argsMap['response'])){ + return res.json({"authorized": false}) + } + const HA1 = crypto.createHash('md5').update(`${argsMap['username']}:${argsMap['realm']}:${password}`).digest('hex') + const HA2 = crypto.createHash('md5').update(`GET:/digest-auth`).digest('hex') + const responseCheck = crypto.createHash('md5').update(`${HA1}:${argsMap['nonce']}:${HA2}`).digest('hex') + + return res.json({"authorized":responseCheck===argsMap['response']}) } // Hawk Auth @Get('hawk-auth') - hawkAuth() { - return 'yet to implement' - // return { authenticated: true } + async hawkAuth(@Req() req, @Response() res) { + const credentialsFunc = function (id) { + + if (id !=='dh37fgj492je'){ + return undefined + } + + const credentials:Credentials = { + key: 'werxhqb98rpaxn39848xrunpaw3489ruxnpa98w4rxn', + algorithm: 'sha256', + user: 'Steve' + }; + + return credentials; + }; + + let status, message + + try { + const { credentials, artifacts } = await Hawk.server.authenticate(req, credentialsFunc) + message = {'message':"Hawk Authentication Successfull"} + console.log() + status = 200; + const header = Hawk.server.header(credentials, artifacts); + + res.set({'Server-Authorization':header}) + } catch (error) { + message = error.output.payload; + status = 401; + const header = error.output.headers + res.set(header) + } + + return res.status(status).json(message) } // OAuth 1.0 @@ -165,20 +247,40 @@ export class RestController { // set cookie @Get('cookies/set') - cookieSet() { - return 'yet to implement' + cookieSet(@Query() queryParams, @Response() res) { + + const payload = {cookies:{}} + + Object.entries(queryParams).forEach((param) => { + const [key, value] = param + payload.cookies[key] = value + res.cookie(key, value) + + }) + console.log(res.cookies) + return res.json(payload) } // get cookie @Get('cookies') - cookieGet() { + cookieGet(@Req() req) { + console.log(req.cookies) return 'yet to implement' } // delete cookie @Get('cookies/delete') - cookieDelete() { - return 'yet to implement' + cookieDelete(@Req() req, @Query() queryParameters, @Response() res) { + + const cookies = {...req.cookies} + console.log(cookies) + + Object.keys(queryParameters).forEach(key => { + res.clearCookie(key) + delete cookies[key] + }); + + return res.json(cookies) } /** Utilities */ @@ -239,8 +341,8 @@ export class RestController { //IP address in JSON format @Get('ip') - ip() { - return 'yet to implement' + ip(@Ip() ip) { + return {'ip': ip} } /** Utilities / Date and Time */ From b7cc7ef764346d3007c93a9282ee9174b7f316c4 Mon Sep 17 00:00:00 2001 From: nkBrew Date: Sun, 15 Oct 2023 13:07:30 -0600 Subject: [PATCH 02/19] compression and encoding --- packages/firecamp-echo-server/package.json | 5 +- packages/firecamp-echo-server/src/main.ts | 12 +- .../src/rest/rest.controller.ts | 59 ++++- packages/firecamp-echo-server/src/test.html | 226 ++++++++++++++++++ 4 files changed, 293 insertions(+), 9 deletions(-) create mode 100644 packages/firecamp-echo-server/src/test.html diff --git a/packages/firecamp-echo-server/package.json b/packages/firecamp-echo-server/package.json index c433a9efc..25d4a7568 100644 --- a/packages/firecamp-echo-server/package.json +++ b/packages/firecamp-echo-server/package.json @@ -27,6 +27,7 @@ "@nestjs/websockets": "9.0.1", "class-transformer": "0.5.1", "class-validator": "0.14.0", + "compression": "^1.7.4", "cookie-parser": "^1.4.6", "hawk": "^9.0.1", "raw-body": "^2.5.2", @@ -36,12 +37,14 @@ "socket.io-v2": "npm:socket.io@2.4.1", "socket.io-v3": "npm:socket.io@3.1.2", "socket.io-v4": "npm:socket.io@4.5.4", - "ws": "8.8.0" + "ws": "8.8.0", + "zlib": "^1.0.5" }, "devDependencies": { "@nestjs/cli": "9.0.0", "@nestjs/schematics": "9.0.1", "@nestjs/testing": "9.0.1", + "@types/compression": "^1.7.3", "@types/cookie-parser": "^1.4.4", "@types/express": "4.17.13", "@types/hawk": "^9.0.4", diff --git a/packages/firecamp-echo-server/src/main.ts b/packages/firecamp-echo-server/src/main.ts index 7697e0657..52862e325 100644 --- a/packages/firecamp-echo-server/src/main.ts +++ b/packages/firecamp-echo-server/src/main.ts @@ -2,16 +2,24 @@ import { NestFactory } from '@nestjs/core'; import { WsAdapter, } from '@nestjs/platform-ws'; // import { IoAdapter } from '@nestjs/platform-socket.io'; import { AppModule } from './app.module'; -import * as cookieParser from 'cookie-parser' +import * as cookieParser from 'cookie-parser'; +import * as compression from 'compression'; +const shouldCompress = (req, res) =>{ + if (req.path === '/gzip'){ + console.log('compressing') + return true + } + return false +} async function bootstrap() { const app = await NestFactory.create(AppModule); app.enableCors(); app.useWebSocketAdapter(new WsAdapter(app)); // app.useWebSocketAdapter(new IoAdapter(app)); - app.use(cookieParser()) + // app.use(compression({threshold:0,filter:() =>true })) await app.listen(3000); console.log(`Application is running on: ${await app.getUrl()}`); diff --git a/packages/firecamp-echo-server/src/rest/rest.controller.ts b/packages/firecamp-echo-server/src/rest/rest.controller.ts index 0ba2eec7f..862c6e03e 100644 --- a/packages/firecamp-echo-server/src/rest/rest.controller.ts +++ b/packages/firecamp-echo-server/src/rest/rest.controller.ts @@ -1,12 +1,17 @@ import * as rawBody from 'raw-body'; -import { BadRequestException, Body, Controller, Get, Headers, Param, Post, Put, Query, Req, Response, HttpStatus, HttpCode, Patch, Delete, Head, Ip } from '@nestjs/common'; +import { BadRequestException, Body, Controller, Get, Headers, Param, Post, Put, Query, Req, Response, HttpStatus, HttpCode, Patch, Delete, Head, Ip, Header, StreamableFile } from '@nestjs/common'; import * as crypto from 'crypto' import { response } from 'express'; import * as Hawk from 'hawk' import { Credentials } from 'hawk/lib/server'; +import { createReadStream } from 'fs'; +import { join } from 'path'; +import {createDeflate, createGzip} from 'zlib'; + const username = 'firecamp' const password = 'password' const nonce = crypto.randomBytes(16).toString('base64') + @Controller('') export class RestController { @@ -323,20 +328,62 @@ export class RestController { // Get UTF8 Encoded Response @Get('encoding/utf8') + @Header('content-type','text/html; charset=utf-8' ) encoding() { - return 'yet to implement' + const file = createReadStream(join(process.cwd(), 'src/test.html')); + return new StreamableFile(file) } // GZip Compressed Response @Get('gzip') - gzip() { - return 'yet to implement' + gzip(@Req() req, @Response() res, @Headers() headers) { + + const responseData = {gzip: true, headers, method:req.method} + + const compressionAlgorithm = 'gzip'; + + const acceptedEncodings = headers['accept-encoding']; + + if (acceptedEncodings && acceptedEncodings.includes(compressionAlgorithm)) { + const jsonResponse = JSON.stringify(responseData); + + res.setHeader('Content-Encoding', compressionAlgorithm); + res.setHeader('Content-Type', 'application/json'); + + const compressionStream = createGzip(); + + compressionStream.pipe(res) + compressionStream.write(jsonResponse) + compressionStream.end() + } else { + res.json({...responseData, gzip:false}); + } } // Deflate Compressed Response @Get('deflate') - deflate() { - return 'yet to implement' + deflate(@Req() req, @Response() res, @Headers() headers) { + + const responseData = {deflate: true, headers, method:req.method} + + const compressionAlgorithm = 'deflate'; + + const acceptedEncodings = headers['accept-encoding']; + + if (acceptedEncodings && acceptedEncodings.includes(compressionAlgorithm)) { + const jsonResponse = JSON.stringify(responseData); + + res.setHeader('Content-Encoding', compressionAlgorithm); + res.setHeader('Content-Type', 'application/json'); + + const compressionStream = createDeflate(); + + compressionStream.pipe(res) + compressionStream.write(jsonResponse) + compressionStream.end() + } else { + res.json({...responseData, deflate:false}); + } } //IP address in JSON format diff --git a/packages/firecamp-echo-server/src/test.html b/packages/firecamp-echo-server/src/test.html new file mode 100644 index 000000000..48d3a8e4f --- /dev/null +++ b/packages/firecamp-echo-server/src/test.html @@ -0,0 +1,226 @@ + + + +

Unicode Demo

+ +

Taken from + http://www.cl.cam.ac.uk/~mgk25/ucs/examples/UTF-8-demo.txt

+ +
+
+        UTF-8 encoded sample plain-text file
+        ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
+
+        Markus Kuhn [ˈmaʳkʊs kuːn]  — 2002-07-25
+
+
+        The ASCII compatible UTF-8 encoding used in this plain-text file
+        is defined in Unicode, ISO 10646-1, and RFC 2279.
+
+
+        Using Unicode/UTF-8, you can write in emails and source code things such as
+
+        Mathematics and sciences:
+
+          ∮ E⋅da = Q,  n → ∞, ∑ f(i) = ∏ g(i),      ⎧⎡⎛┌─────┐⎞⎤⎫
+                                                    ⎪⎢⎜│a²+b³ ⎟⎥⎪
+          ∀x∈ℝ: ⌈x⌉ = −⌊−x⌋, α ∧ ¬β = ¬(¬α ∨ β),    ⎪⎢⎜│───── ⎟⎥⎪
+                                                    ⎪⎢⎜⎷ c₈   ⎟⎥⎪
+          ℕ ⊆ ℕ₀ ⊂ ℤ ⊂ ℚ ⊂ ℝ ⊂ ℂ,                   ⎨⎢⎜       ⎟⎥⎬
+                                                    ⎪⎢⎜ ∞     ⎟⎥⎪
+          ⊥ < a ≠ b ≡ c ≤ d ≪ ⊤ ⇒ (⟦A⟧ ⇔ ⟪B⟫),      ⎪⎢⎜ ⎲     ⎟⎥⎪
+                                                    ⎪⎢⎜ ⎳aⁱ-bⁱ⎟⎥⎪
+          2H₂ + O₂ ⇌ 2H₂O, R = 4.7 kΩ, ⌀ 200 mm     ⎩⎣⎝i=1    ⎠⎦⎭
+
+        Linguistics and dictionaries:
+
+          ði ıntəˈnæʃənəl fəˈnɛtık əsoʊsiˈeıʃn
+          Y [ˈʏpsilɔn], Yen [jɛn], Yoga [ˈjoːgɑ]
+
+        APL:
+
+          ((V⍳V)=⍳⍴V)/V←,V    ⌷←⍳→⍴∆∇⊃‾⍎⍕⌈
+
+        Nicer typography in plain text files:
+
+          ╔══════════════════════════════════════════╗
+          ║                                          ║
+          ║   • ‘single’ and “double” quotes         ║
+          ║                                          ║
+          ║   • Curly apostrophes: “We’ve been here” ║
+          ║                                          ║
+          ║   • Latin-1 apostrophe and accents: '´`  ║
+          ║                                          ║
+          ║   • ‚deutsche‘ „Anführungszeichen“       ║
+          ║                                          ║
+          ║   • †, ‡, ‰, •, 3–4, —, −5/+5, ™, …      ║
+          ║                                          ║
+          ║   • ASCII safety test: 1lI|, 0OD, 8B     ║
+          ║                      ╭─────────╮         ║
+          ║   • the euro symbol: │ 14.95 € │         ║
+          ║                      ╰─────────╯         ║
+          ╚══════════════════════════════════════════╝
+
+        Combining characters:
+
+          STARGΛ̊TE SG-1, a = v̇ = r̈, a⃑ ⊥ b⃑
+
+        Greek (in Polytonic):
+
+          The Greek anthem:
+
+          Σὲ γνωρίζω ἀπὸ τὴν κόψη
+          τοῦ σπαθιοῦ τὴν τρομερή,
+          σὲ γνωρίζω ἀπὸ τὴν ὄψη
+          ποὺ μὲ βία μετράει τὴ γῆ.
+
+          ᾿Απ᾿ τὰ κόκκαλα βγαλμένη
+          τῶν ῾Ελλήνων τὰ ἱερά
+          καὶ σὰν πρῶτα ἀνδρειωμένη
+          χαῖρε, ὦ χαῖρε, ᾿Ελευθεριά!
+
+          From a speech of Demosthenes in the 4th century BC:
+
+          Οὐχὶ ταὐτὰ παρίσταταί μοι γιγνώσκειν, ὦ ἄνδρες ᾿Αθηναῖοι,
+          ὅταν τ᾿ εἰς τὰ πράγματα ἀποβλέψω καὶ ὅταν πρὸς τοὺς
+          λόγους οὓς ἀκούω· τοὺς μὲν γὰρ λόγους περὶ τοῦ
+          τιμωρήσασθαι Φίλιππον ὁρῶ γιγνομένους, τὰ δὲ πράγματ᾿
+          εἰς τοῦτο προήκοντα,  ὥσθ᾿ ὅπως μὴ πεισόμεθ᾿ αὐτοὶ
+          πρότερον κακῶς σκέψασθαι δέον. οὐδέν οὖν ἄλλο μοι δοκοῦσιν
+          οἱ τὰ τοιαῦτα λέγοντες ἢ τὴν ὑπόθεσιν, περὶ ἧς βουλεύεσθαι,
+          οὐχὶ τὴν οὖσαν παριστάντες ὑμῖν ἁμαρτάνειν. ἐγὼ δέ, ὅτι μέν
+          ποτ᾿ ἐξῆν τῇ πόλει καὶ τὰ αὑτῆς ἔχειν ἀσφαλῶς καὶ Φίλιππον
+          τιμωρήσασθαι, καὶ μάλ᾿ ἀκριβῶς οἶδα· ἐπ᾿ ἐμοῦ γάρ, οὐ πάλαι
+          γέγονεν ταῦτ᾿ ἀμφότερα· νῦν μέντοι πέπεισμαι τοῦθ᾿ ἱκανὸν
+          προλαβεῖν ἡμῖν εἶναι τὴν πρώτην, ὅπως τοὺς συμμάχους
+          σώσομεν. ἐὰν γὰρ τοῦτο βεβαίως ὑπάρξῃ, τότε καὶ περὶ τοῦ
+          τίνα τιμωρήσεταί τις καὶ ὃν τρόπον ἐξέσται σκοπεῖν· πρὶν δὲ
+          τὴν ἀρχὴν ὀρθῶς ὑποθέσθαι, μάταιον ἡγοῦμαι περὶ τῆς
+          τελευτῆς ὁντινοῦν ποιεῖσθαι λόγον.
+
+          Δημοσθένους, Γ´ ᾿Ολυνθιακὸς
+
+        Georgian:
+
+          From a Unicode conference invitation:
+
+          გთხოვთ ახლავე გაიაროთ რეგისტრაცია Unicode-ის მეათე საერთაშორისო
+          კონფერენციაზე დასასწრებად, რომელიც გაიმართება 10-12 მარტს,
+          ქ. მაინცში, გერმანიაში. კონფერენცია შეჰკრებს ერთად მსოფლიოს
+          ექსპერტებს ისეთ დარგებში როგორიცაა ინტერნეტი და Unicode-ი,
+          ინტერნაციონალიზაცია და ლოკალიზაცია, Unicode-ის გამოყენება
+          ოპერაციულ სისტემებსა, და გამოყენებით პროგრამებში, შრიფტებში,
+          ტექსტების დამუშავებასა და მრავალენოვან კომპიუტერულ სისტემებში.
+
+        Russian:
+
+          From a Unicode conference invitation:
+
+          Зарегистрируйтесь сейчас на Десятую Международную Конференцию по
+          Unicode, которая состоится 10-12 марта 1997 года в Майнце в Германии.
+          Конференция соберет широкий круг экспертов по  вопросам глобального
+          Интернета и Unicode, локализации и интернационализации, воплощению и
+          применению Unicode в различных операционных системах и программных
+          приложениях, шрифтах, верстке и многоязычных компьютерных системах.
+
+        Thai (UCS Level 2):
+
+          Excerpt from a poetry on The Romance of The Three Kingdoms (a Chinese
+          classic 'San Gua'):
+
+          [----------------------------|------------------------]
+            ๏ แผ่นดินฮั่นเสื่อมโทรมแสนสังเวช  พระปกเกศกองบู๊กู้ขึ้นใหม่
+          สิบสองกษัตริย์ก่อนหน้าแลถัดไป       สององค์ไซร้โง่เขลาเบาปัญญา
+            ทรงนับถือขันทีเป็นที่พึ่ง           บ้านเมืองจึงวิปริตเป็นนักหนา
+          โฮจิ๋นเรียกทัพทั่วหัวเมืองมา         หมายจะฆ่ามดชั่วตัวสำคัญ
+            เหมือนขับไสไล่เสือจากเคหา      รับหมาป่าเข้ามาเลยอาสัญ
+          ฝ่ายอ้องอุ้นยุแยกให้แตกกัน          ใช้สาวนั้นเป็นชนวนชื่นชวนใจ
+            พลันลิฉุยกุยกีกลับก่อเหตุ          ช่างอาเพศจริงหนาฟ้าร้องไห้
+          ต้องรบราฆ่าฟันจนบรรลัย           ฤๅหาใครค้ำชูกู้บรรลังก์ ฯ
+
+          (The above is a two-column text. If combining characters are handled
+          correctly, the lines of the second column should be aligned with the
+          | character above.)
+
+        Ethiopian:
+
+          Proverbs in the Amharic language:
+
+          ሰማይ አይታረስ ንጉሥ አይከሰስ።
+          ብላ ካለኝ እንደአባቴ በቆመጠኝ።
+          ጌጥ ያለቤቱ ቁምጥና ነው።
+          ደሀ በሕልሙ ቅቤ ባይጠጣ ንጣት በገደለው።
+          የአፍ ወለምታ በቅቤ አይታሽም።
+          አይጥ በበላ ዳዋ ተመታ።
+          ሲተረጉሙ ይደረግሙ።
+          ቀስ በቀስ፥ ዕንቁላል በእግሩ ይሄዳል።
+          ድር ቢያብር አንበሳ ያስር።
+          ሰው እንደቤቱ እንጅ እንደ ጉረቤቱ አይተዳደርም።
+          እግዜር የከፈተውን ጉሮሮ ሳይዘጋው አይድርም።
+          የጎረቤት ሌባ፥ ቢያዩት ይስቅ ባያዩት ያጠልቅ።
+          ሥራ ከመፍታት ልጄን ላፋታት።
+          ዓባይ ማደሪያ የለው፥ ግንድ ይዞ ይዞራል።
+          የእስላም አገሩ መካ የአሞራ አገሩ ዋርካ።
+          ተንጋሎ ቢተፉ ተመልሶ ባፉ።
+          ወዳጅህ ማር ቢሆን ጨርስህ አትላሰው።
+          እግርህን በፍራሽህ ልክ ዘርጋ።
+
+        Runes:
+
+          ᚻᛖ ᚳᚹᚫᚦ ᚦᚫᛏ ᚻᛖ ᛒᚢᛞᛖ ᚩᚾ ᚦᚫᛗ ᛚᚪᚾᛞᛖ ᚾᚩᚱᚦᚹᛖᚪᚱᛞᚢᛗ ᚹᛁᚦ ᚦᚪ ᚹᛖᛥᚫ
+
+          (Old English, which transcribed into Latin reads 'He cwaeth that he
+          bude thaem lande northweardum with tha Westsae.' and means 'He said
+          that he lived in the northern land near the Western Sea.')
+
+        Braille:
+
+          ⡌⠁⠧⠑ ⠼⠁⠒  ⡍⠜⠇⠑⠹⠰⠎ ⡣⠕⠌
+
+          ⡍⠜⠇⠑⠹ ⠺⠁⠎ ⠙⠑⠁⠙⠒ ⠞⠕ ⠃⠑⠛⠔ ⠺⠊⠹⠲ ⡹⠻⠑ ⠊⠎ ⠝⠕ ⠙⠳⠃⠞
+          ⠱⠁⠞⠑⠧⠻ ⠁⠃⠳⠞ ⠹⠁⠞⠲ ⡹⠑ ⠗⠑⠛⠊⠌⠻ ⠕⠋ ⠙⠊⠎ ⠃⠥⠗⠊⠁⠇ ⠺⠁⠎
+          ⠎⠊⠛⠝⠫ ⠃⠹ ⠹⠑ ⠊⠇⠻⠛⠹⠍⠁⠝⠂ ⠹⠑ ⠊⠇⠻⠅⠂ ⠹⠑ ⠥⠝⠙⠻⠞⠁⠅⠻⠂
+          ⠁⠝⠙ ⠹⠑ ⠡⠊⠑⠋ ⠍⠳⠗⠝⠻⠲ ⡎⠊⠗⠕⠕⠛⠑ ⠎⠊⠛⠝⠫ ⠊⠞⠲ ⡁⠝⠙
+          ⡎⠊⠗⠕⠕⠛⠑⠰⠎ ⠝⠁⠍⠑ ⠺⠁⠎ ⠛⠕⠕⠙ ⠥⠏⠕⠝ ⠰⡡⠁⠝⠛⠑⠂ ⠋⠕⠗ ⠁⠝⠹⠹⠔⠛ ⠙⠑
+          ⠡⠕⠎⠑ ⠞⠕ ⠏⠥⠞ ⠙⠊⠎ ⠙⠁⠝⠙ ⠞⠕⠲
+
+          ⡕⠇⠙ ⡍⠜⠇⠑⠹ ⠺⠁⠎ ⠁⠎ ⠙⠑⠁⠙ ⠁⠎ ⠁ ⠙⠕⠕⠗⠤⠝⠁⠊⠇⠲
+
+          ⡍⠔⠙⠖ ⡊ ⠙⠕⠝⠰⠞ ⠍⠑⠁⠝ ⠞⠕ ⠎⠁⠹ ⠹⠁⠞ ⡊ ⠅⠝⠪⠂ ⠕⠋ ⠍⠹
+          ⠪⠝ ⠅⠝⠪⠇⠫⠛⠑⠂ ⠱⠁⠞ ⠹⠻⠑ ⠊⠎ ⠏⠜⠞⠊⠊⠥⠇⠜⠇⠹ ⠙⠑⠁⠙ ⠁⠃⠳⠞
+          ⠁ ⠙⠕⠕⠗⠤⠝⠁⠊⠇⠲ ⡊ ⠍⠊⠣⠞ ⠙⠁⠧⠑ ⠃⠑⠲ ⠔⠊⠇⠔⠫⠂ ⠍⠹⠎⠑⠇⠋⠂ ⠞⠕
+          ⠗⠑⠛⠜⠙ ⠁ ⠊⠕⠋⠋⠔⠤⠝⠁⠊⠇ ⠁⠎ ⠹⠑ ⠙⠑⠁⠙⠑⠌ ⠏⠊⠑⠊⠑ ⠕⠋ ⠊⠗⠕⠝⠍⠕⠝⠛⠻⠹
+          ⠔ ⠹⠑ ⠞⠗⠁⠙⠑⠲ ⡃⠥⠞ ⠹⠑ ⠺⠊⠎⠙⠕⠍ ⠕⠋ ⠳⠗ ⠁⠝⠊⠑⠌⠕⠗⠎
+          ⠊⠎ ⠔ ⠹⠑ ⠎⠊⠍⠊⠇⠑⠆ ⠁⠝⠙ ⠍⠹ ⠥⠝⠙⠁⠇⠇⠪⠫ ⠙⠁⠝⠙⠎
+          ⠩⠁⠇⠇ ⠝⠕⠞ ⠙⠊⠌⠥⠗⠃ ⠊⠞⠂ ⠕⠗ ⠹⠑ ⡊⠳⠝⠞⠗⠹⠰⠎ ⠙⠕⠝⠑ ⠋⠕⠗⠲ ⡹⠳
+          ⠺⠊⠇⠇ ⠹⠻⠑⠋⠕⠗⠑ ⠏⠻⠍⠊⠞ ⠍⠑ ⠞⠕ ⠗⠑⠏⠑⠁⠞⠂ ⠑⠍⠏⠙⠁⠞⠊⠊⠁⠇⠇⠹⠂ ⠹⠁⠞
+          ⡍⠜⠇⠑⠹ ⠺⠁⠎ ⠁⠎ ⠙⠑⠁⠙ ⠁⠎ ⠁ ⠙⠕⠕⠗⠤⠝⠁⠊⠇⠲
+
+          (The first couple of paragraphs of "A Christmas Carol" by Dickens)
+
+        Compact font selection example text:
+
+          ABCDEFGHIJKLMNOPQRSTUVWXYZ /0123456789
+          abcdefghijklmnopqrstuvwxyz £©µÀÆÖÞßéöÿ
+          –—‘“”„†•…‰™œŠŸž€ ΑΒΓΔΩαβγδω АБВГДабвгд
+          ∀∂∈ℝ∧∪≡∞ ↑↗↨↻⇣ ┐┼╔╘░►☺♀ fi�⑀₂ἠḂӥẄɐː⍎אԱა
+
+        Greetings in various languages:
+
+          Hello world, Καλημέρα κόσμε, コンニチハ
+
+        Box drawing alignment tests:                                          █
+                                                                              ▉
+          ╔══╦══╗  ┌──┬──┐  ╭──┬──╮  ╭──┬──╮  ┏━━┳━━┓  ┎┒┏┑   ╷  ╻ ┏┯┓ ┌┰┐    ▊ ╱╲╱╲╳╳╳
+          ║┌─╨─┐║  │╔═╧═╗│  │╒═╪═╕│  │╓─╁─╖│  ┃┌─╂─┐┃  ┗╃╄┙  ╶┼╴╺╋╸┠┼┨ ┝╋┥    ▋ ╲╱╲╱╳╳╳
+          ║│╲ ╱│║  │║   ║│  ││ │ ││  │║ ┃ ║│  ┃│ ╿ │┃  ┍╅╆┓   ╵  ╹ ┗┷┛ └┸┘    ▌ ╱╲╱╲╳╳╳
+          ╠╡ ╳ ╞╣  ├╢   ╟┤  ├┼─┼─┼┤  ├╫─╂─╫┤  ┣┿╾┼╼┿┫  ┕┛┖┚     ┌┄┄┐ ╎ ┏┅┅┓ ┋ ▍ ╲╱╲╱╳╳╳
+          ║│╱ ╲│║  │║   ║│  ││ │ ││  │║ ┃ ║│  ┃│ ╽ │┃  ░░▒▒▓▓██ ┊  ┆ ╎ ╏  ┇ ┋ ▎
+          ║└─╥─┘║  │╚═╤═╝│  │╘═╪═╛│  │╙─╀─╜│  ┃└─╂─┘┃  ░░▒▒▓▓██ ┊  ┆ ╎ ╏  ┇ ┋ ▏
+          ╚══╩══╝  └──┴──┘  ╰──┴──╯  ╰──┴──╯  ┗━━┻━━┛  ▗▄▖▛▀▜   └╌╌┘ ╎ ┗╍╍┛ ┋  ▁▂▃▄▅▆▇█
+                                                       ▝▀▘▙▄▟
+
+        
+ + + \ No newline at end of file From 99ce7b4fc09275e6f5c52b497041bf7fccc9dc6c Mon Sep 17 00:00:00 2001 From: nkBrew Date: Mon, 16 Oct 2023 08:36:39 -0600 Subject: [PATCH 03/19] oauth signature verification --- packages/firecamp-echo-server/package.json | 3 + .../src/rest/rest.controller.ts | 96 ++++++++++++++++++- 2 files changed, 96 insertions(+), 3 deletions(-) diff --git a/packages/firecamp-echo-server/package.json b/packages/firecamp-echo-server/package.json index 25d4a7568..95987bbe2 100644 --- a/packages/firecamp-echo-server/package.json +++ b/packages/firecamp-echo-server/package.json @@ -30,6 +30,8 @@ "compression": "^1.7.4", "cookie-parser": "^1.4.6", "hawk": "^9.0.1", + "oauth": "^0.10.0", + "oauth-1.0a": "^2.2.6", "raw-body": "^2.5.2", "reflect-metadata": "0.1.13", "rimraf": "3.0.2", @@ -49,6 +51,7 @@ "@types/express": "4.17.13", "@types/hawk": "^9.0.4", "@types/node": "18.0.3", + "@types/oauth": "^0.9.2", "@types/supertest": "2.0.12", "@types/ws": "8.5.3", "@typescript-eslint/eslint-plugin": "5.30.5", diff --git a/packages/firecamp-echo-server/src/rest/rest.controller.ts b/packages/firecamp-echo-server/src/rest/rest.controller.ts index 862c6e03e..4877c4034 100644 --- a/packages/firecamp-echo-server/src/rest/rest.controller.ts +++ b/packages/firecamp-echo-server/src/rest/rest.controller.ts @@ -7,11 +7,37 @@ import { Credentials } from 'hawk/lib/server'; import { createReadStream } from 'fs'; import { join } from 'path'; import {createDeflate, createGzip} from 'zlib'; +import * as OAuth from 'oauth-1.0a' const username = 'firecamp' const password = 'password' const nonce = crypto.randomBytes(16).toString('base64') +const consumerKey = 'RKCGzna7bv9YD57c'; +const consumerSecret = 'D+EdQ-gs$-%@2Nu7'; + +// Create an instance of the OAuth class + +function buildSignatureBase(httpMethod, baseUrl, oauthParameters) { + // Sort the OAuth parameters alphabetically by name + const sortedParameters = Object.keys(oauthParameters) + .sort() + .filter(key => key !== 'oauth_signature') + .map(key => key + '=' + encodeURIComponent(oauthParameters[key])); + + // Create the parameter string by joining the sorted parameters with "&" + const parameterString = sortedParameters.join('&'); + + // Encode the HTTP method and base URL + const encodedHttpMethod = encodeURIComponent(httpMethod); + const encodedBaseUrl = encodeURIComponent(baseUrl); + + // Construct the signature base string + const signatureBase = `${encodedHttpMethod}&${encodedBaseUrl}&${encodeURIComponent(parameterString)}`; + + return signatureBase; + } + @Controller('') export class RestController { @@ -243,9 +269,73 @@ export class RestController { // OAuth 1.0 @Get('oauth1') - OAuth1() { - return 'yet to implement' - // return { authenticated: true } + OAuth1(@Req() req, @Headers() headers, @Response() res) { + + const parseOAuthHeader = (authorizationHeader) =>{ + const oauthParams = {}; + + // Regular expression to match OAuth parameters in the Authorization header + const oauthRegex = /(\w+)="([^"]+)"/g; + + let match; + while ((match = oauthRegex.exec(authorizationHeader)) !== null) { + oauthParams[match[1]] = match[2]; + } + + return oauthParams; + } + + // const oauthParams = {}; + const authorizationHeader = headers.authorization + + + const oauthParams = parseOAuthHeader(authorizationHeader.slice(6)) + console.log(oauthParams) + const consumerKey = oauthParams['oauth_consumer_key']; + const timestamp = oauthParams['oauth_timestamp']; + const nonce = oauthParams['oauth_nonce']; + const signature = oauthParams['oauth_signature']; + const baseString = `${req.method}&${encodeURIComponent(`${req.protocol}://${req.get('Host')}${req.originalUrl}`)}&${encodeURIComponent(`oauth_consumer_key=${consumerKey}&oauth_nonce=${nonce}&oauth_timestamp=${timestamp}`)}`; + console.log(baseString) + const signatureBase = buildSignatureBase('GET', 'http://localhost:3000/oauth1', oauthParams); + console.log(signatureBase) + + console.log(`${req.protocol}://${req.get('Host')}${req.originalUrl}`) + // console.log(req.accessUrl) + // const oauth = new OAuth( + // req.url, + // req.accessUrl, + // consumerKey, + // consumerSecret, + // '1.0', + // null, + // 'HMAC-SHA1' + // ); + + // oauth.signUrl + // oauth. + + // Calculate the expected signature using the consumer secret + const expectedSignature = crypto.createHmac('sha1', consumerSecret) + .update(baseString) + .digest('base64'); + const expectedSignature2 = crypto.createHmac('sha1', 'D%2BEdQ-gs%24-%25%402Nu7&') + .update(signatureBase) + .digest('base64'); + console.log(encodeURIComponent('D+EdQ-gs$-%@2Nu7'+'&')) + + console.log(expectedSignature) + console.log(encodeURIComponent(expectedSignature2)) + console.log(expectedSignature2) + console.log(decodeURIComponent(signature)) + // // Compare the expected signature with the signature in the request + // if (signature === expectedSignature) { + // return res.json({ message: 'Signature verified successfully' }); + // } else { + // return res.status(401).json({ error: 'Signature verification failed' }); + // } + // // return 'yet to implement' + return res.json({ authenticated: true }) } /** Cookies Manipulation*/ From 810f45cf4e8d27474d7c256d4f3d9f908c5e832a Mon Sep 17 00:00:00 2001 From: nkBrew Date: Mon, 16 Oct 2023 08:45:37 -0600 Subject: [PATCH 04/19] cleanup --- packages/firecamp-echo-server/package.json | 2 -- packages/firecamp-echo-server/src/main.ts | 9 ------- .../src/rest/rest.controller.ts | 24 ++++--------------- 3 files changed, 4 insertions(+), 31 deletions(-) diff --git a/packages/firecamp-echo-server/package.json b/packages/firecamp-echo-server/package.json index 95987bbe2..f756d0245 100644 --- a/packages/firecamp-echo-server/package.json +++ b/packages/firecamp-echo-server/package.json @@ -27,10 +27,8 @@ "@nestjs/websockets": "9.0.1", "class-transformer": "0.5.1", "class-validator": "0.14.0", - "compression": "^1.7.4", "cookie-parser": "^1.4.6", "hawk": "^9.0.1", - "oauth": "^0.10.0", "oauth-1.0a": "^2.2.6", "raw-body": "^2.5.2", "reflect-metadata": "0.1.13", diff --git a/packages/firecamp-echo-server/src/main.ts b/packages/firecamp-echo-server/src/main.ts index 52862e325..fd10a6018 100644 --- a/packages/firecamp-echo-server/src/main.ts +++ b/packages/firecamp-echo-server/src/main.ts @@ -3,15 +3,7 @@ import { WsAdapter, } from '@nestjs/platform-ws'; // import { IoAdapter } from '@nestjs/platform-socket.io'; import { AppModule } from './app.module'; import * as cookieParser from 'cookie-parser'; -import * as compression from 'compression'; -const shouldCompress = (req, res) =>{ - if (req.path === '/gzip'){ - console.log('compressing') - return true - } - return false -} async function bootstrap() { const app = await NestFactory.create(AppModule); app.enableCors(); @@ -19,7 +11,6 @@ async function bootstrap() { app.useWebSocketAdapter(new WsAdapter(app)); // app.useWebSocketAdapter(new IoAdapter(app)); app.use(cookieParser()) - // app.use(compression({threshold:0,filter:() =>true })) await app.listen(3000); console.log(`Application is running on: ${await app.getUrl()}`); diff --git a/packages/firecamp-echo-server/src/rest/rest.controller.ts b/packages/firecamp-echo-server/src/rest/rest.controller.ts index 4877c4034..2eb541816 100644 --- a/packages/firecamp-echo-server/src/rest/rest.controller.ts +++ b/packages/firecamp-echo-server/src/rest/rest.controller.ts @@ -7,7 +7,6 @@ import { Credentials } from 'hawk/lib/server'; import { createReadStream } from 'fs'; import { join } from 'path'; import {createDeflate, createGzip} from 'zlib'; -import * as OAuth from 'oauth-1.0a' const username = 'firecamp' const password = 'password' @@ -16,8 +15,6 @@ const nonce = crypto.randomBytes(16).toString('base64') const consumerKey = 'RKCGzna7bv9YD57c'; const consumerSecret = 'D+EdQ-gs$-%@2Nu7'; -// Create an instance of the OAuth class - function buildSignatureBase(httpMethod, baseUrl, oauthParameters) { // Sort the OAuth parameters alphabetically by name const sortedParameters = Object.keys(oauthParameters) @@ -285,37 +282,24 @@ export class RestController { return oauthParams; } - // const oauthParams = {}; const authorizationHeader = headers.authorization - const oauthParams = parseOAuthHeader(authorizationHeader.slice(6)) + console.log(oauthParams) + const consumerKey = oauthParams['oauth_consumer_key']; const timestamp = oauthParams['oauth_timestamp']; const nonce = oauthParams['oauth_nonce']; const signature = oauthParams['oauth_signature']; const baseString = `${req.method}&${encodeURIComponent(`${req.protocol}://${req.get('Host')}${req.originalUrl}`)}&${encodeURIComponent(`oauth_consumer_key=${consumerKey}&oauth_nonce=${nonce}&oauth_timestamp=${timestamp}`)}`; + console.log(baseString) const signatureBase = buildSignatureBase('GET', 'http://localhost:3000/oauth1', oauthParams); console.log(signatureBase) console.log(`${req.protocol}://${req.get('Host')}${req.originalUrl}`) - // console.log(req.accessUrl) - // const oauth = new OAuth( - // req.url, - // req.accessUrl, - // consumerKey, - // consumerSecret, - // '1.0', - // null, - // 'HMAC-SHA1' - // ); - - // oauth.signUrl - // oauth. - - // Calculate the expected signature using the consumer secret + const expectedSignature = crypto.createHmac('sha1', consumerSecret) .update(baseString) .digest('base64'); From 28efbea2d78bd07bf6afd845ecacff52b28e5307 Mon Sep 17 00:00:00 2001 From: nkBrew Date: Mon, 16 Oct 2023 11:35:37 -0600 Subject: [PATCH 05/19] Stream --- .../src/rest/rest.controller.ts | 26 +++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/packages/firecamp-echo-server/src/rest/rest.controller.ts b/packages/firecamp-echo-server/src/rest/rest.controller.ts index 2eb541816..10a390ad3 100644 --- a/packages/firecamp-echo-server/src/rest/rest.controller.ts +++ b/packages/firecamp-echo-server/src/rest/rest.controller.ts @@ -7,6 +7,7 @@ import { Credentials } from 'hawk/lib/server'; import { createReadStream } from 'fs'; import { join } from 'path'; import {createDeflate, createGzip} from 'zlib'; +import { Readable } from 'stream'; const username = 'firecamp' const password = 'password' @@ -380,10 +381,31 @@ export class RestController { // Streamed Response @Get('stream/:chunk') stream( + @Req() req, @Param('chunk') chunk, - @Response() res + @Headers() headers, ) { - return 'yet to implement' + console.log(headers) + const n = parseInt(chunk) + + if (isNaN(n)){ + return + } + + let responseJson = "" + for(let i =0; i< n; i++){ + const args = {n: chunk} + const url = `${req.protocol}://${req.get('Host')}${req.originalUrl}` + const responseData = {args,headers, url} + responseJson += JSON.stringify(responseData, null, 2) + } + + const responseBuffer = Buffer.from(responseJson) + const stream = new Readable() + stream.push(responseBuffer) + stream.push(null) + + return new StreamableFile(stream) } // Delayed Response From 23a4fda40b74cd470748e5cb712fe9a3c9e1fc88 Mon Sep 17 00:00:00 2001 From: nkBrew Date: Mon, 16 Oct 2023 11:40:09 -0600 Subject: [PATCH 06/19] get cookies --- packages/firecamp-echo-server/src/rest/rest.controller.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/firecamp-echo-server/src/rest/rest.controller.ts b/packages/firecamp-echo-server/src/rest/rest.controller.ts index 10a390ad3..13d086a47 100644 --- a/packages/firecamp-echo-server/src/rest/rest.controller.ts +++ b/packages/firecamp-echo-server/src/rest/rest.controller.ts @@ -343,9 +343,10 @@ export class RestController { // get cookie @Get('cookies') - cookieGet(@Req() req) { + cookieGet(@Req() req, @Response() res) { console.log(req.cookies) - return 'yet to implement' + const responseData = {cookies: req.cookies} + return res.json(responseData) } // delete cookie @@ -404,7 +405,7 @@ export class RestController { const stream = new Readable() stream.push(responseBuffer) stream.push(null) - + return new StreamableFile(stream) } From 8430e1b8af850b12bf9a0427dd6efa233153c24c Mon Sep 17 00:00:00 2001 From: nkBrew Date: Mon, 16 Oct 2023 11:41:35 -0600 Subject: [PATCH 07/19] Cleanup --- packages/firecamp-echo-server/package.json | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/firecamp-echo-server/package.json b/packages/firecamp-echo-server/package.json index f756d0245..31177df01 100644 --- a/packages/firecamp-echo-server/package.json +++ b/packages/firecamp-echo-server/package.json @@ -29,7 +29,6 @@ "class-validator": "0.14.0", "cookie-parser": "^1.4.6", "hawk": "^9.0.1", - "oauth-1.0a": "^2.2.6", "raw-body": "^2.5.2", "reflect-metadata": "0.1.13", "rimraf": "3.0.2", @@ -49,7 +48,6 @@ "@types/express": "4.17.13", "@types/hawk": "^9.0.4", "@types/node": "18.0.3", - "@types/oauth": "^0.9.2", "@types/supertest": "2.0.12", "@types/ws": "8.5.3", "@typescript-eslint/eslint-plugin": "5.30.5", From e8230efc6373185ec38f40e4827616c05bd30077 Mon Sep 17 00:00:00 2001 From: nkBrew Date: Mon, 16 Oct 2023 18:13:50 -0600 Subject: [PATCH 08/19] Cleanup and bug fixing --- .../{test.html => assets/unicodedemo.html} | 0 .../src/rest/rest.controller.ts | 122 ++++++++++-------- 2 files changed, 67 insertions(+), 55 deletions(-) rename packages/firecamp-echo-server/src/{test.html => assets/unicodedemo.html} (100%) diff --git a/packages/firecamp-echo-server/src/test.html b/packages/firecamp-echo-server/src/assets/unicodedemo.html similarity index 100% rename from packages/firecamp-echo-server/src/test.html rename to packages/firecamp-echo-server/src/assets/unicodedemo.html diff --git a/packages/firecamp-echo-server/src/rest/rest.controller.ts b/packages/firecamp-echo-server/src/rest/rest.controller.ts index 13d086a47..276a416e4 100644 --- a/packages/firecamp-echo-server/src/rest/rest.controller.ts +++ b/packages/firecamp-echo-server/src/rest/rest.controller.ts @@ -9,9 +9,10 @@ import { join } from 'path'; import {createDeflate, createGzip} from 'zlib'; import { Readable } from 'stream'; -const username = 'firecamp' -const password = 'password' -const nonce = crypto.randomBytes(16).toString('base64') +const echo_username = 'firecamp' +const echo_password = 'password' +const hawk_id = 'dh37fgj492je' +// const nonce = crypto.randomBytes(16).toString('base64') const consumerKey = 'RKCGzna7bv9YD57c'; const consumerSecret = 'D+EdQ-gs$-%@2Nu7'; @@ -159,8 +160,8 @@ export class RestController { // @Headers() headers, @Response() res ) { - // console.log(queryParams) - Object.keys(queryParams).map((k, i)=> { + + Object.keys(queryParams).map((k)=> { res.header(k, queryParams[k]); }) return res.json(queryParams); @@ -170,23 +171,30 @@ export class RestController { // Basic Auth @Get('basic-auth') - basicAuth(@Query() queryParams, @Headers() headers) { + basicAuth(@Query() queryParams, @Headers() headers, @Response() res) { + if (!('authorization' in headers )){ - return {authenticated:false} + return res.status(401).send('Unauthorized') } + const split = headers.authorization.split(" ") + const type = split[0] if (type != 'Basic'){ - return {authenticated:false} + return res.status(401).send('Unauthorized') + } const decoded = Buffer.from(split[1], 'base64').toString() const decodedSplit = decoded.split(':') if (decodedSplit.length != 2){ - return {authenticated:false} + return res.status(401).send('Unauthorized') } const username = decodedSplit[0] const password = decodedSplit[1] - console.log(username, password) + + if (username !== echo_username || password !== echo_password){ + return res.status(401).send('Unauthorized') + } return { authenticated: true } } @@ -197,34 +205,38 @@ export class RestController { const realm = 'Users' if (!('authorization' in headers)){ + const nonce = crypto.randomBytes(16).toString('base64'); res.set({'www-authenticate':`Digest realm="${realm}", nonce="${nonce}"`}).status(401); - return res.json('Unauthorized') + return res.status(401).send('Unauthorized') } - const split = headers.authorization.replace(',','').split(" ") + const split = headers.authorization.split(" ") const type = split[0] - if (type != 'Digest'){ - return res.json('Unauthorized') + if (type !== 'Digest'){ + return res.status(401).send('Unauthorized') } - const {authorization} = headers + - const authArgs = authorization.slice(7).split(', ') + const authArgs = headers.authorization.slice(7).split(', ') const argsMap = {} - console.log(authArgs) + authArgs.forEach(arg => { const split = arg.replaceAll('"','').replace('=', '-:-').split('-:-') argsMap[split[0]] = split[1] }) - console.log(argsMap) if (!(argsMap['username'] && argsMap['nonce'] && argsMap['realm'] && argsMap['response'])){ return res.json({"authorized": false}) } - const HA1 = crypto.createHash('md5').update(`${argsMap['username']}:${argsMap['realm']}:${password}`).digest('hex') + const HA1 = crypto.createHash('md5').update(`${argsMap['username']}:${argsMap['realm']}:${echo_password}`).digest('hex') const HA2 = crypto.createHash('md5').update(`GET:/digest-auth`).digest('hex') const responseCheck = crypto.createHash('md5').update(`${HA1}:${argsMap['nonce']}:${HA2}`).digest('hex') + + if (responseCheck !== argsMap['response']){ + return res.status(401).send('Unauthorized') + } - return res.json({"authorized":responseCheck===argsMap['response']}) + return res.json({"authorized":true}) } // Hawk Auth @@ -250,7 +262,7 @@ export class RestController { try { const { credentials, artifacts } = await Hawk.server.authenticate(req, credentialsFunc) message = {'message':"Hawk Authentication Successfull"} - console.log() + status = 200; const header = Hawk.server.header(credentials, artifacts); @@ -281,46 +293,48 @@ export class RestController { } return oauthParams; - } + } + + if (!('authorization' in headers )){ + return res.status(401).send('Unauthorized') + } const authorizationHeader = headers.authorization + if (authorizationHeader.split(' ')[0] !== 'OAuth'){ + return res.status(401).send('Unauthorized') + } + const oauthParams = parseOAuthHeader(authorizationHeader.slice(6)) - console.log(oauthParams) - const consumerKey = oauthParams['oauth_consumer_key']; const timestamp = oauthParams['oauth_timestamp']; const nonce = oauthParams['oauth_nonce']; - const signature = oauthParams['oauth_signature']; - const baseString = `${req.method}&${encodeURIComponent(`${req.protocol}://${req.get('Host')}${req.originalUrl}`)}&${encodeURIComponent(`oauth_consumer_key=${consumerKey}&oauth_nonce=${nonce}&oauth_timestamp=${timestamp}`)}`; + const signature = decodeURIComponent(oauthParams['oauth_signature']); + const signatureMethod = oauthParams['oauth_signature_method']; - console.log(baseString) - const signatureBase = buildSignatureBase('GET', 'http://localhost:3000/oauth1', oauthParams); - console.log(signatureBase) - console.log(`${req.protocol}://${req.get('Host')}${req.originalUrl}`) + const base_uri = `${req.protocol}://${req.get('Host')}${req.originalUrl}` + const signatureBase = buildSignatureBase('GET', base_uri, oauthParams); - const expectedSignature = crypto.createHmac('sha1', consumerSecret) - .update(baseString) - .digest('base64'); - const expectedSignature2 = crypto.createHmac('sha1', 'D%2BEdQ-gs%24-%25%402Nu7&') - .update(signatureBase) - .digest('base64'); - console.log(encodeURIComponent('D+EdQ-gs$-%@2Nu7'+'&')) - - console.log(expectedSignature) - console.log(encodeURIComponent(expectedSignature2)) - console.log(expectedSignature2) - console.log(decodeURIComponent(signature)) - // // Compare the expected signature with the signature in the request - // if (signature === expectedSignature) { - // return res.json({ message: 'Signature verified successfully' }); - // } else { - // return res.status(401).json({ error: 'Signature verification failed' }); - // } - // // return 'yet to implement' - return res.json({ authenticated: true }) + const signingKey = 'D%2BEdQ-gs%24-%25%402Nu7&' + + const expectedSignature = crypto.createHmac('sha1', signingKey) + .update(signatureBase) + .digest('base64'); + + if (signature !== expectedSignature) { + const status = 'fail' + const message = "HMAC-SHA1 verification failed" + const normalized_param_string = consumerKey+'&'+nonce+'&'+signatureMethod+'&'+timestamp + const baseString = signatureBase + return res.status(401).json({status, message, base_uri, normalized_param_string, base_string:baseString, signing_key:signingKey}); + } + const status = 'pass' + const message = 'OAuth-1.0a signature verification was successful' + + + return res.json({status, message }) } /** Cookies Manipulation*/ @@ -337,14 +351,13 @@ export class RestController { res.cookie(key, value) }) - console.log(res.cookies) return res.json(payload) } // get cookie @Get('cookies') cookieGet(@Req() req, @Response() res) { - console.log(req.cookies) + const responseData = {cookies: req.cookies} return res.json(responseData) } @@ -354,7 +367,6 @@ export class RestController { cookieDelete(@Req() req, @Query() queryParameters, @Response() res) { const cookies = {...req.cookies} - console.log(cookies) Object.keys(queryParameters).forEach(key => { res.clearCookie(key) @@ -386,7 +398,7 @@ export class RestController { @Param('chunk') chunk, @Headers() headers, ) { - console.log(headers) + const n = parseInt(chunk) if (isNaN(n)){ @@ -427,7 +439,7 @@ export class RestController { @Get('encoding/utf8') @Header('content-type','text/html; charset=utf-8' ) encoding() { - const file = createReadStream(join(process.cwd(), 'src/test.html')); + const file = createReadStream(join(process.cwd(), 'src/assets/unicodedemo.html')); return new StreamableFile(file) } From d1d94c83919c4bc328efeaea7b4f311bd9629a11 Mon Sep 17 00:00:00 2001 From: nkBrew Date: Tue, 17 Oct 2023 08:13:28 -0600 Subject: [PATCH 09/19] Spelling exclusion --- .github/actions/spelling/excludes.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/actions/spelling/excludes.txt b/.github/actions/spelling/excludes.txt index b3135d244..68c95909d 100644 --- a/.github/actions/spelling/excludes.txt +++ b/.github/actions/spelling/excludes.txt @@ -88,4 +88,5 @@ ^\Qpackages/firecamp-collection-runner/readme.md\E$ ^\Qplatform/firecamp-platform/__tests__/index.spec.ts\E$ ^\Qpnpm-lock.yaml\E$ +^\Qpackages/firecamp-echo-server/src/assets/unicodedemo.html\E$ ignore$ From eb51a7fa373b2eb6e2a31eb7fe511870284bfeec Mon Sep 17 00:00:00 2001 From: nkBrew Date: Sun, 22 Oct 2023 16:02:03 -0600 Subject: [PATCH 10/19] added words to exclude.txt --- .github/actions/spelling/expect.txt | 2 ++ .../src/assets/unicodedemo.html | 2 +- .../src/rest/rest.controller.ts | 29 +++++++++---------- 3 files changed, 17 insertions(+), 16 deletions(-) diff --git a/.github/actions/spelling/expect.txt b/.github/actions/spelling/expect.txt index 5e7434fa3..96401c3b1 100644 --- a/.github/actions/spelling/expect.txt +++ b/.github/actions/spelling/expect.txt @@ -247,6 +247,7 @@ socketio someplugin someuser SSLn +Streamable subrip szh szhsin @@ -279,6 +280,7 @@ TTr Twotone typcn typesjs +unicodedemo unpressurized urlbar usagestop diff --git a/packages/firecamp-echo-server/src/assets/unicodedemo.html b/packages/firecamp-echo-server/src/assets/unicodedemo.html index 48d3a8e4f..d3917136e 100644 --- a/packages/firecamp-echo-server/src/assets/unicodedemo.html +++ b/packages/firecamp-echo-server/src/assets/unicodedemo.html @@ -223,4 +223,4 @@

Unicode Demo

- \ No newline at end of file + diff --git a/packages/firecamp-echo-server/src/rest/rest.controller.ts b/packages/firecamp-echo-server/src/rest/rest.controller.ts index 276a416e4..ce7bcf908 100644 --- a/packages/firecamp-echo-server/src/rest/rest.controller.ts +++ b/packages/firecamp-echo-server/src/rest/rest.controller.ts @@ -12,7 +12,6 @@ import { Readable } from 'stream'; const echo_username = 'firecamp' const echo_password = 'password' const hawk_id = 'dh37fgj492je' -// const nonce = crypto.randomBytes(16).toString('base64') const consumerKey = 'RKCGzna7bv9YD57c'; const consumerSecret = 'D+EdQ-gs$-%@2Nu7'; @@ -244,8 +243,8 @@ export class RestController { async hawkAuth(@Req() req, @Response() res) { const credentialsFunc = function (id) { - if (id !=='dh37fgj492je'){ - return undefined + if (id !==hawk_id){ + return undefined; } const credentials:Credentials = { @@ -257,11 +256,11 @@ export class RestController { return credentials; }; - let status, message + let status, message; try { - const { credentials, artifacts } = await Hawk.server.authenticate(req, credentialsFunc) - message = {'message':"Hawk Authentication Successfull"} + const { credentials, artifacts } = await Hawk.server.authenticate(req, credentialsFunc); + message = {'message':"Hawk Authentication Successful"}; status = 200; const header = Hawk.server.header(credentials, artifacts); @@ -270,11 +269,11 @@ export class RestController { } catch (error) { message = error.output.payload; status = 401; - const header = error.output.headers - res.set(header) + const header = error.output.headers; + res.set(header); } - return res.status(status).json(message) + return res.status(status).json(message); } // OAuth 1.0 @@ -296,13 +295,13 @@ export class RestController { } if (!('authorization' in headers )){ - return res.status(401).send('Unauthorized') + return res.status(401).send('Unauthorized'); } - const authorizationHeader = headers.authorization + const authorizationHeader = headers.authorization; if (authorizationHeader.split(' ')[0] !== 'OAuth'){ - return res.status(401).send('Unauthorized') + return res.status(401).send('Unauthorized'); } const oauthParams = parseOAuthHeader(authorizationHeader.slice(6)) @@ -314,12 +313,12 @@ export class RestController { const signatureMethod = oauthParams['oauth_signature_method']; - const base_uri = `${req.protocol}://${req.get('Host')}${req.originalUrl}` + const base_uri = `${req.protocol}://${req.get('Host')}${req.originalUrl}`; const signatureBase = buildSignatureBase('GET', base_uri, oauthParams); - const signingKey = 'D%2BEdQ-gs%24-%25%402Nu7&' + const signingKey = 'D%2BEdQ-gs%24-%25%402Nu7&'; - const expectedSignature = crypto.createHmac('sha1', signingKey) + const expectedSignature = crypto.createHmac('sha1', signingKey); .update(signatureBase) .digest('base64'); From 8f49350e03f57d2ca465e6f2844dcf4e3f53726e Mon Sep 17 00:00:00 2001 From: nkBrew Date: Sun, 22 Oct 2023 16:02:49 -0600 Subject: [PATCH 11/19] formatted file --- .../src/rest/rest.controller.ts | 932 +++++++++--------- 1 file changed, 478 insertions(+), 454 deletions(-) diff --git a/packages/firecamp-echo-server/src/rest/rest.controller.ts b/packages/firecamp-echo-server/src/rest/rest.controller.ts index ce7bcf908..15f05f88a 100644 --- a/packages/firecamp-echo-server/src/rest/rest.controller.ts +++ b/packages/firecamp-echo-server/src/rest/rest.controller.ts @@ -1,519 +1,543 @@ import * as rawBody from 'raw-body'; -import { BadRequestException, Body, Controller, Get, Headers, Param, Post, Put, Query, Req, Response, HttpStatus, HttpCode, Patch, Delete, Head, Ip, Header, StreamableFile } from '@nestjs/common'; -import * as crypto from 'crypto' +import { + BadRequestException, + Body, + Controller, + Get, + Headers, + Param, + Post, + Put, + Query, + Req, + Response, + HttpStatus, + HttpCode, + Patch, + Delete, + Head, + Ip, + Header, + StreamableFile, +} from '@nestjs/common'; +import * as crypto from 'crypto'; import { response } from 'express'; -import * as Hawk from 'hawk' +import * as Hawk from 'hawk'; import { Credentials } from 'hawk/lib/server'; import { createReadStream } from 'fs'; import { join } from 'path'; -import {createDeflate, createGzip} from 'zlib'; +import { createDeflate, createGzip } from 'zlib'; import { Readable } from 'stream'; -const echo_username = 'firecamp' -const echo_password = 'password' -const hawk_id = 'dh37fgj492je' +const echo_username = 'firecamp'; +const echo_password = 'password'; +const hawk_id = 'dh37fgj492je'; const consumerKey = 'RKCGzna7bv9YD57c'; const consumerSecret = 'D+EdQ-gs$-%@2Nu7'; function buildSignatureBase(httpMethod, baseUrl, oauthParameters) { - // Sort the OAuth parameters alphabetically by name - const sortedParameters = Object.keys(oauthParameters) - .sort() - .filter(key => key !== 'oauth_signature') - .map(key => key + '=' + encodeURIComponent(oauthParameters[key])); - - // Create the parameter string by joining the sorted parameters with "&" - const parameterString = sortedParameters.join('&'); - - // Encode the HTTP method and base URL - const encodedHttpMethod = encodeURIComponent(httpMethod); - const encodedBaseUrl = encodeURIComponent(baseUrl); - - // Construct the signature base string - const signatureBase = `${encodedHttpMethod}&${encodedBaseUrl}&${encodeURIComponent(parameterString)}`; - - return signatureBase; - } + // Sort the OAuth parameters alphabetically by name + const sortedParameters = Object.keys(oauthParameters) + .sort() + .filter((key) => key !== 'oauth_signature') + .map((key) => key + '=' + encodeURIComponent(oauthParameters[key])); + + // Create the parameter string by joining the sorted parameters with "&" + const parameterString = sortedParameters.join('&'); + + // Encode the HTTP method and base URL + const encodedHttpMethod = encodeURIComponent(httpMethod); + const encodedBaseUrl = encodeURIComponent(baseUrl); + + // Construct the signature base string + const signatureBase = `${encodedHttpMethod}&${encodedBaseUrl}&${encodeURIComponent( + parameterString + )}`; + + return signatureBase; +} @Controller('') export class RestController { + /** Methods */ - /** Methods */ + @Get('get') + get(@Req() req, @Query() queryParams, @Headers() headers) { + const url = `${req.protocol}://${req.get('Host')}${req.originalUrl}`; + const method = 'GET'; + return { url, method, args: queryParams, headers }; + } - @Get('get') - get( - @Req() req, - @Query() queryParams, - @Headers() headers - ) { - const url = `${req.protocol}://${req.get('Host')}${req.originalUrl}` - const method = 'GET'; - return { url, method, args: queryParams, headers } + @Post('post') + @HttpCode(HttpStatus.OK) + async post( + @Req() req, + @Body() body, + @Headers('Content-Type') ct, + @Headers() headers + ) { + const url = `${req.protocol}://${req.get('Host')}${req.originalUrl}`; + const method = 'POST'; + if (req.readable) { + // body is ignored by NestJS -> get raw body from request + const raw = await rawBody(req); + body = raw.toString().trim(); } + let data = {}; + let form = {}; + if (ct == 'application/x-www-form-urlencoded') form = body; + else data = body; + return { url, method, form, data, headers }; + } - @Post('post') - @HttpCode(HttpStatus.OK) - async post( - @Req() req, - @Body() body, - @Headers('Content-Type') ct, - @Headers() headers - ) { - const url = `${req.protocol}://${req.get('Host')}${req.originalUrl}` - const method = 'POST'; - if (req.readable) { - // body is ignored by NestJS -> get raw body from request - const raw = await rawBody(req); - body = raw.toString().trim(); - } - let data= {}; - let form = {}; - if(ct == 'application/x-www-form-urlencoded') form = body; - else data = body - return { url, method, form, data, headers } + @Put('put') + async put( + @Req() req, + @Body() body, + @Headers('Content-Type') ct, + @Headers() headers + ) { + const url = `${req.protocol}://${req.get('Host')}${req.originalUrl}`; + const method = 'PUT'; + if (req.readable) { + // body is ignored by NestJS -> get raw body from request + const raw = await rawBody(req); + body = raw.toString().trim(); } + let data = {}; + let form = {}; + if (ct == 'application/x-www-form-urlencoded') form = body; + else data = body; + return { url, method, form, data, headers }; + } - @Put('put') - async put( - @Req() req, - @Body() body, - @Headers('Content-Type') ct, - @Headers() headers - ) { - const url = `${req.protocol}://${req.get('Host')}${req.originalUrl}` - const method = 'PUT'; - if (req.readable) { - // body is ignored by NestJS -> get raw body from request - const raw = await rawBody(req); - body = raw.toString().trim(); - } - let data= {}; - let form = {}; - if(ct == 'application/x-www-form-urlencoded') form = body; - else data = body - return { url, method, form, data, headers } + @Patch('patch') + async patch( + @Req() req, + @Body() body, + @Headers('Content-Type') ct, + @Headers() headers + ) { + const url = `${req.protocol}://${req.get('Host')}${req.originalUrl}`; + const method = 'PATCH'; + if (req.readable) { + // body is ignored by NestJS -> get raw body from request + const raw = await rawBody(req); + body = raw.toString().trim(); } - @Patch('patch') - async patch( - @Req() req, - @Body() body, - @Headers('Content-Type') ct, - @Headers() headers - ) { - const url = `${req.protocol}://${req.get('Host')}${req.originalUrl}` - const method = 'PATCH'; - if (req.readable) { - // body is ignored by NestJS -> get raw body from request - const raw = await rawBody(req); - body = raw.toString().trim(); - } - - let data= {}; - let form = {}; - if(ct == 'application/x-www-form-urlencoded') form = body; - else data = body - return { url, method, form, data, headers } - } + let data = {}; + let form = {}; + if (ct == 'application/x-www-form-urlencoded') form = body; + else data = body; + return { url, method, form, data, headers }; + } - @Delete('delete') - async delete( - @Req() req, - @Body() body, - @Headers('Content-Type') ct, - @Headers() headers - ) { - const url = `${req.protocol}://${req.get('Host')}${req.originalUrl}` - const method = 'PATCH'; - if (req.readable) { - // body is ignored by NestJS -> get raw body from request - const raw = await rawBody(req); - body = raw.toString().trim(); - } - - let data= {}; - let form = {}; - if(ct == 'application/x-www-form-urlencoded') form = body; - else data = body - return { url, method, form, data, headers } + @Delete('delete') + async delete( + @Req() req, + @Body() body, + @Headers('Content-Type') ct, + @Headers() headers + ) { + const url = `${req.protocol}://${req.get('Host')}${req.originalUrl}`; + const method = 'PATCH'; + if (req.readable) { + // body is ignored by NestJS -> get raw body from request + const raw = await rawBody(req); + body = raw.toString().trim(); } + let data = {}; + let form = {}; + if (ct == 'application/x-www-form-urlencoded') form = body; + else data = body; + return { url, method, form, data, headers }; + } - /** Headers */ + /** Headers */ - @Get('headers') - headers( - @Req() req, - @Headers() headers - ) { - // const url = `${req.protocol}://${req.get('Host')}${req.originalUrl}` - // const method = 'GET'; - return { headers } + @Get('headers') + headers(@Req() req, @Headers() headers) { + // const url = `${req.protocol}://${req.get('Host')}${req.originalUrl}` + // const method = 'GET'; + return { headers }; + } + + @Get('response-headers') + responseHeaders( + @Req() req, + @Query() queryParams, + // @Headers() headers, + @Response() res + ) { + Object.keys(queryParams).map((k) => { + res.header(k, queryParams[k]); + }); + return res.json(queryParams); + } + + /** Authentication Methods */ + + // Basic Auth + @Get('basic-auth') + basicAuth(@Query() queryParams, @Headers() headers, @Response() res) { + if (!('authorization' in headers)) { + return res.status(401).send('Unauthorized'); } - @Get('response-headers') - responseHeaders( - @Req() req, - @Query() queryParams, - // @Headers() headers, - @Response() res - ) { - - Object.keys(queryParams).map((k)=> { - res.header(k, queryParams[k]); - }) - return res.json(queryParams); + const split = headers.authorization.split(' '); + + const type = split[0]; + if (type != 'Basic') { + return res.status(401).send('Unauthorized'); } + const decoded = Buffer.from(split[1], 'base64').toString(); + const decodedSplit = decoded.split(':'); + if (decodedSplit.length != 2) { + return res.status(401).send('Unauthorized'); + } + const username = decodedSplit[0]; + const password = decodedSplit[1]; - /** Authentication Methods */ - - // Basic Auth - @Get('basic-auth') - basicAuth(@Query() queryParams, @Headers() headers, @Response() res) { - - if (!('authorization' in headers )){ - return res.status(401).send('Unauthorized') - } - - const split = headers.authorization.split(" ") - - const type = split[0] - if (type != 'Basic'){ - return res.status(401).send('Unauthorized') - - } - const decoded = Buffer.from(split[1], 'base64').toString() - const decodedSplit = decoded.split(':') - if (decodedSplit.length != 2){ - return res.status(401).send('Unauthorized') - } - const username = decodedSplit[0] - const password = decodedSplit[1] - - if (username !== echo_username || password !== echo_password){ - return res.status(401).send('Unauthorized') - } - - return { authenticated: true } + if (username !== echo_username || password !== echo_password) { + return res.status(401).send('Unauthorized'); } - // Digest Auth - @Get('digest-auth') - digestAuth(@Req() req,@Headers() headers, @Response() res) { - const realm = 'Users' - - if (!('authorization' in headers)){ - const nonce = crypto.randomBytes(16).toString('base64'); - res.set({'www-authenticate':`Digest realm="${realm}", nonce="${nonce}"`}).status(401); - return res.status(401).send('Unauthorized') - } - - const split = headers.authorization.split(" ") - const type = split[0] - if (type !== 'Digest'){ - return res.status(401).send('Unauthorized') - } - - - const authArgs = headers.authorization.slice(7).split(', ') - const argsMap = {} - - authArgs.forEach(arg => { - const split = arg.replaceAll('"','').replace('=', '-:-').split('-:-') - argsMap[split[0]] = split[1] + return { authenticated: true }; + } + + // Digest Auth + @Get('digest-auth') + digestAuth(@Req() req, @Headers() headers, @Response() res) { + const realm = 'Users'; + + if (!('authorization' in headers)) { + const nonce = crypto.randomBytes(16).toString('base64'); + res + .set({ + 'www-authenticate': `Digest realm="${realm}", nonce="${nonce}"`, }) - - if (!(argsMap['username'] && argsMap['nonce'] && argsMap['realm'] && argsMap['response'])){ - return res.json({"authorized": false}) - } - const HA1 = crypto.createHash('md5').update(`${argsMap['username']}:${argsMap['realm']}:${echo_password}`).digest('hex') - const HA2 = crypto.createHash('md5').update(`GET:/digest-auth`).digest('hex') - const responseCheck = crypto.createHash('md5').update(`${HA1}:${argsMap['nonce']}:${HA2}`).digest('hex') - - if (responseCheck !== argsMap['response']){ - return res.status(401).send('Unauthorized') - } - - return res.json({"authorized":true}) + .status(401); + return res.status(401).send('Unauthorized'); + } + + const split = headers.authorization.split(' '); + const type = split[0]; + if (type !== 'Digest') { + return res.status(401).send('Unauthorized'); } - // Hawk Auth - @Get('hawk-auth') - async hawkAuth(@Req() req, @Response() res) { - const credentialsFunc = function (id) { - - if (id !==hawk_id){ - return undefined; - } - - const credentials:Credentials = { - key: 'werxhqb98rpaxn39848xrunpaw3489ruxnpa98w4rxn', - algorithm: 'sha256', - user: 'Steve' - }; - - return credentials; - }; - - let status, message; - - try { - const { credentials, artifacts } = await Hawk.server.authenticate(req, credentialsFunc); - message = {'message':"Hawk Authentication Successful"}; - - status = 200; - const header = Hawk.server.header(credentials, artifacts); - - res.set({'Server-Authorization':header}) - } catch (error) { - message = error.output.payload; - status = 401; - const header = error.output.headers; - res.set(header); - } - - return res.status(status).json(message); + const authArgs = headers.authorization.slice(7).split(', '); + const argsMap = {}; + + authArgs.forEach((arg) => { + const split = arg.replaceAll('"', '').replace('=', '-:-').split('-:-'); + argsMap[split[0]] = split[1]; + }); + + if ( + !( + argsMap['username'] && + argsMap['nonce'] && + argsMap['realm'] && + argsMap['response'] + ) + ) { + return res.json({ authorized: false }); + } + const HA1 = crypto + .createHash('md5') + .update(`${argsMap['username']}:${argsMap['realm']}:${echo_password}`) + .digest('hex'); + const HA2 = crypto + .createHash('md5') + .update(`GET:/digest-auth`) + .digest('hex'); + const responseCheck = crypto + .createHash('md5') + .update(`${HA1}:${argsMap['nonce']}:${HA2}`) + .digest('hex'); + + if (responseCheck !== argsMap['response']) { + return res.status(401).send('Unauthorized'); } - // OAuth 1.0 - @Get('oauth1') - OAuth1(@Req() req, @Headers() headers, @Response() res) { - - const parseOAuthHeader = (authorizationHeader) =>{ - const oauthParams = {}; - - // Regular expression to match OAuth parameters in the Authorization header - const oauthRegex = /(\w+)="([^"]+)"/g; - - let match; - while ((match = oauthRegex.exec(authorizationHeader)) !== null) { - oauthParams[match[1]] = match[2]; - } - - return oauthParams; - } - - if (!('authorization' in headers )){ - return res.status(401).send('Unauthorized'); - } - - const authorizationHeader = headers.authorization; - - if (authorizationHeader.split(' ')[0] !== 'OAuth'){ - return res.status(401).send('Unauthorized'); - } - - const oauthParams = parseOAuthHeader(authorizationHeader.slice(6)) - - const consumerKey = oauthParams['oauth_consumer_key']; - const timestamp = oauthParams['oauth_timestamp']; - const nonce = oauthParams['oauth_nonce']; - const signature = decodeURIComponent(oauthParams['oauth_signature']); - const signatureMethod = oauthParams['oauth_signature_method']; - - - const base_uri = `${req.protocol}://${req.get('Host')}${req.originalUrl}`; - const signatureBase = buildSignatureBase('GET', base_uri, oauthParams); - - const signingKey = 'D%2BEdQ-gs%24-%25%402Nu7&'; - - const expectedSignature = crypto.createHmac('sha1', signingKey); - .update(signatureBase) - .digest('base64'); - - if (signature !== expectedSignature) { - const status = 'fail' - const message = "HMAC-SHA1 verification failed" - const normalized_param_string = consumerKey+'&'+nonce+'&'+signatureMethod+'&'+timestamp - const baseString = signatureBase - return res.status(401).json({status, message, base_uri, normalized_param_string, base_string:baseString, signing_key:signingKey}); - } - const status = 'pass' - const message = 'OAuth-1.0a signature verification was successful' - - - return res.json({status, message }) + return res.json({ authorized: true }); + } + + // Hawk Auth + @Get('hawk-auth') + async hawkAuth(@Req() req, @Response() res) { + const credentialsFunc = function (id) { + if (id !== hawk_id) { + return undefined; + } + + const credentials: Credentials = { + key: 'werxhqb98rpaxn39848xrunpaw3489ruxnpa98w4rxn', + algorithm: 'sha256', + user: 'Steve', + }; + + return credentials; + }; + + let status, message; + + try { + const { credentials, artifacts } = await Hawk.server.authenticate( + req, + credentialsFunc + ); + message = { message: 'Hawk Authentication Successful' }; + + status = 200; + const header = Hawk.server.header(credentials, artifacts); + + res.set({ 'Server-Authorization': header }); + } catch (error) { + message = error.output.payload; + status = 401; + const header = error.output.headers; + res.set(header); } - /** Cookies Manipulation*/ + return res.status(status).json(message); + } - // set cookie - @Get('cookies/set') - cookieSet(@Query() queryParams, @Response() res) { - - const payload = {cookies:{}} + // OAuth 1.0 + @Get('oauth1') + OAuth1(@Req() req, @Headers() headers, @Response() res) { + const parseOAuthHeader = (authorizationHeader) => { + const oauthParams = {}; - Object.entries(queryParams).forEach((param) => { - const [key, value] = param - payload.cookies[key] = value - res.cookie(key, value) - - }) - return res.json(payload) + // Regular expression to match OAuth parameters in the Authorization header + const oauthRegex = /(\w+)="([^"]+)"/g; + + let match; + while ((match = oauthRegex.exec(authorizationHeader)) !== null) { + oauthParams[match[1]] = match[2]; + } + + return oauthParams; + }; + + if (!('authorization' in headers)) { + return res.status(401).send('Unauthorized'); } - // get cookie - @Get('cookies') - cookieGet(@Req() req, @Response() res) { - - const responseData = {cookies: req.cookies} - return res.json(responseData) + const authorizationHeader = headers.authorization; + + if (authorizationHeader.split(' ')[0] !== 'OAuth') { + return res.status(401).send('Unauthorized'); } - // delete cookie - @Get('cookies/delete') - cookieDelete(@Req() req, @Query() queryParameters, @Response() res) { - - const cookies = {...req.cookies} - - Object.keys(queryParameters).forEach(key => { - res.clearCookie(key) - delete cookies[key] + const oauthParams = parseOAuthHeader(authorizationHeader.slice(6)); + + const consumerKey = oauthParams['oauth_consumer_key']; + const timestamp = oauthParams['oauth_timestamp']; + const nonce = oauthParams['oauth_nonce']; + const signature = decodeURIComponent(oauthParams['oauth_signature']); + const signatureMethod = oauthParams['oauth_signature_method']; + + const base_uri = `${req.protocol}://${req.get('Host')}${req.originalUrl}`; + const signatureBase = buildSignatureBase('GET', base_uri, oauthParams); + + const signingKey = 'D%2BEdQ-gs%24-%25%402Nu7&'; + + const expectedSignature = crypto + .createHmac('sha1', signingKey) + .update(signatureBase) + .digest('base64'); + + if (signature !== expectedSignature) { + const status = 'fail'; + const message = 'HMAC-SHA1 verification failed'; + const normalized_param_string = + consumerKey + '&' + nonce + '&' + signatureMethod + '&' + timestamp; + const baseString = signatureBase; + return res + .status(401) + .json({ + status, + message, + base_uri, + normalized_param_string, + base_string: baseString, + signing_key: signingKey, }); - - return res.json(cookies) } + const status = 'pass'; + const message = 'OAuth-1.0a signature verification was successful'; - /** Utilities */ + return res.json({ status, message }); + } - // Response Status Code - @Get('status/:status') - status( - @Param('status') status, - @Response() res - ) { - try { - return res.status(status).json({ status }) - } catch (e) { - throw new BadRequestException(e); - } - } + /** Cookies Manipulation*/ - // Streamed Response - @Get('stream/:chunk') - stream( - @Req() req, - @Param('chunk') chunk, - @Headers() headers, - ) { - - const n = parseInt(chunk) - - if (isNaN(n)){ - return - } - - let responseJson = "" - for(let i =0; i< n; i++){ - const args = {n: chunk} - const url = `${req.protocol}://${req.get('Host')}${req.originalUrl}` - const responseData = {args,headers, url} - responseJson += JSON.stringify(responseData, null, 2) - } - - const responseBuffer = Buffer.from(responseJson) - const stream = new Readable() - stream.push(responseBuffer) - stream.push(null) - - return new StreamableFile(stream) - } + // set cookie + @Get('cookies/set') + cookieSet(@Query() queryParams, @Response() res) { + const payload = { cookies: {} }; - // Delayed Response - @Get('delay/:seconds') - delay( - @Param('seconds') seconds, - @Response() res - ) { + Object.entries(queryParams).forEach((param) => { + const [key, value] = param; + payload.cookies[key] = value; + res.cookie(key, value); + }); + return res.json(payload); + } + + // get cookie + @Get('cookies') + cookieGet(@Req() req, @Response() res) { + const responseData = { cookies: req.cookies }; + return res.json(responseData); + } - seconds = +seconds; - if (typeof seconds != 'number') throw new BadRequestException('Seconds not valid') - setTimeout(() => { - return res.json({ delay: seconds, in: 'seconds' }) - }, seconds * 1000); + // delete cookie + @Get('cookies/delete') + cookieDelete(@Req() req, @Query() queryParameters, @Response() res) { + const cookies = { ...req.cookies }; + + Object.keys(queryParameters).forEach((key) => { + res.clearCookie(key); + delete cookies[key]; + }); + + return res.json(cookies); + } + + /** Utilities */ + + // Response Status Code + @Get('status/:status') + status(@Param('status') status, @Response() res) { + try { + return res.status(status).json({ status }); + } catch (e) { + throw new BadRequestException(e); } + } + + // Streamed Response + @Get('stream/:chunk') + stream(@Req() req, @Param('chunk') chunk, @Headers() headers) { + const n = parseInt(chunk); - // Get UTF8 Encoded Response - @Get('encoding/utf8') - @Header('content-type','text/html; charset=utf-8' ) - encoding() { - const file = createReadStream(join(process.cwd(), 'src/assets/unicodedemo.html')); - return new StreamableFile(file) + if (isNaN(n)) { + return; } - // GZip Compressed Response - @Get('gzip') - gzip(@Req() req, @Response() res, @Headers() headers) { - - const responseData = {gzip: true, headers, method:req.method} - - const compressionAlgorithm = 'gzip'; - - const acceptedEncodings = headers['accept-encoding']; - - if (acceptedEncodings && acceptedEncodings.includes(compressionAlgorithm)) { - const jsonResponse = JSON.stringify(responseData); - - res.setHeader('Content-Encoding', compressionAlgorithm); - res.setHeader('Content-Type', 'application/json'); - - const compressionStream = createGzip(); - - compressionStream.pipe(res) - compressionStream.write(jsonResponse) - compressionStream.end() - } else { - res.json({...responseData, gzip:false}); - } + let responseJson = ''; + for (let i = 0; i < n; i++) { + const args = { n: chunk }; + const url = `${req.protocol}://${req.get('Host')}${req.originalUrl}`; + const responseData = { args, headers, url }; + responseJson += JSON.stringify(responseData, null, 2); } - // Deflate Compressed Response - @Get('deflate') - deflate(@Req() req, @Response() res, @Headers() headers) { - - const responseData = {deflate: true, headers, method:req.method} - - const compressionAlgorithm = 'deflate'; - - const acceptedEncodings = headers['accept-encoding']; - - if (acceptedEncodings && acceptedEncodings.includes(compressionAlgorithm)) { - const jsonResponse = JSON.stringify(responseData); - - res.setHeader('Content-Encoding', compressionAlgorithm); - res.setHeader('Content-Type', 'application/json'); - - const compressionStream = createDeflate(); - - compressionStream.pipe(res) - compressionStream.write(jsonResponse) - compressionStream.end() - } else { - res.json({...responseData, deflate:false}); - } + const responseBuffer = Buffer.from(responseJson); + const stream = new Readable(); + stream.push(responseBuffer); + stream.push(null); + + return new StreamableFile(stream); + } + + // Delayed Response + @Get('delay/:seconds') + delay(@Param('seconds') seconds, @Response() res) { + seconds = +seconds; + if (typeof seconds != 'number') + throw new BadRequestException('Seconds not valid'); + setTimeout(() => { + return res.json({ delay: seconds, in: 'seconds' }); + }, seconds * 1000); + } + + // Get UTF8 Encoded Response + @Get('encoding/utf8') + @Header('content-type', 'text/html; charset=utf-8') + encoding() { + const file = createReadStream( + join(process.cwd(), 'src/assets/unicodedemo.html') + ); + return new StreamableFile(file); + } + + // GZip Compressed Response + @Get('gzip') + gzip(@Req() req, @Response() res, @Headers() headers) { + const responseData = { gzip: true, headers, method: req.method }; + + const compressionAlgorithm = 'gzip'; + + const acceptedEncodings = headers['accept-encoding']; + + if (acceptedEncodings && acceptedEncodings.includes(compressionAlgorithm)) { + const jsonResponse = JSON.stringify(responseData); + + res.setHeader('Content-Encoding', compressionAlgorithm); + res.setHeader('Content-Type', 'application/json'); + + const compressionStream = createGzip(); + + compressionStream.pipe(res); + compressionStream.write(jsonResponse); + compressionStream.end(); + } else { + res.json({ ...responseData, gzip: false }); } + } + + // Deflate Compressed Response + @Get('deflate') + deflate(@Req() req, @Response() res, @Headers() headers) { + const responseData = { deflate: true, headers, method: req.method }; + + const compressionAlgorithm = 'deflate'; + + const acceptedEncodings = headers['accept-encoding']; + + if (acceptedEncodings && acceptedEncodings.includes(compressionAlgorithm)) { + const jsonResponse = JSON.stringify(responseData); - //IP address in JSON format - @Get('ip') - ip(@Ip() ip) { - return {'ip': ip} + res.setHeader('Content-Encoding', compressionAlgorithm); + res.setHeader('Content-Type', 'application/json'); + + const compressionStream = createDeflate(); + + compressionStream.pipe(res); + compressionStream.write(jsonResponse); + compressionStream.end(); + } else { + res.json({ ...responseData, deflate: false }); } + } + + //IP address in JSON format + @Get('ip') + ip(@Ip() ip) { + return { ip: ip }; + } - /** Utilities / Date and Time */ - // 1. Current UTC time - // 2. Timestamp validity - // 3. Format timestamp - // 4. Extract timestamp unit - // 5. Time addition - // 6. Time subtraction - // 7. Start of time - // 8. Object representation - // 9. Before comparisons - // 10. After comparisons - // 11. Between timestamps - // 12. Leap year check - - /** Auth: Digest */ - // 1. DigestAuth Request + /** Utilities / Date and Time */ + // 1. Current UTC time + // 2. Timestamp validity + // 3. Format timestamp + // 4. Extract timestamp unit + // 5. Time addition + // 6. Time subtraction + // 7. Start of time + // 8. Object representation + // 9. Before comparisons + // 10. After comparisons + // 11. Between timestamps + // 12. Leap year check + + /** Auth: Digest */ + // 1. DigestAuth Request } From 6eb94f632f385aaaf0f943f7ccb2cc4579da6f20 Mon Sep 17 00:00:00 2001 From: nkBrew Date: Sun, 22 Oct 2023 16:16:46 -0600 Subject: [PATCH 12/19] extract credentials to own file --- .github/actions/spelling/excludes.txt | 2 ++ .../src/assets/credentials.js | 6 ++++++ .../src/rest/rest.controller.ts | 17 ++++++----------- 3 files changed, 14 insertions(+), 11 deletions(-) create mode 100644 packages/firecamp-echo-server/src/assets/credentials.js diff --git a/.github/actions/spelling/excludes.txt b/.github/actions/spelling/excludes.txt index 68c95909d..abe6c69c7 100644 --- a/.github/actions/spelling/excludes.txt +++ b/.github/actions/spelling/excludes.txt @@ -90,3 +90,5 @@ ^\Qpnpm-lock.yaml\E$ ^\Qpackages/firecamp-echo-server/src/assets/unicodedemo.html\E$ ignore$ +^\Qpackages/firecamp-echo-server/src/assets/credentials.js\E$ +ignore$ \ No newline at end of file diff --git a/packages/firecamp-echo-server/src/assets/credentials.js b/packages/firecamp-echo-server/src/assets/credentials.js new file mode 100644 index 000000000..389953ff1 --- /dev/null +++ b/packages/firecamp-echo-server/src/assets/credentials.js @@ -0,0 +1,6 @@ +export const echo_username = 'firecamp'; +export const echo_password = 'password'; +export const hawk_id = 'dh37fgj492je'; +export const hawk_key = 'werxhqb98rpaxn39848xrunpaw3489ruxnpa98w4rxn'; +export const oath_signing_key = 'D%2BEdQ-gs%24-%25%402Nu7&' +const consumerSecret = 'D+EdQ-gs$-%@2Nu7' \ No newline at end of file diff --git a/packages/firecamp-echo-server/src/rest/rest.controller.ts b/packages/firecamp-echo-server/src/rest/rest.controller.ts index 15f05f88a..2c7b60d42 100644 --- a/packages/firecamp-echo-server/src/rest/rest.controller.ts +++ b/packages/firecamp-echo-server/src/rest/rest.controller.ts @@ -28,13 +28,10 @@ import { createReadStream } from 'fs'; import { join } from 'path'; import { createDeflate, createGzip } from 'zlib'; import { Readable } from 'stream'; +import {echo_username, echo_password, hawk_id, hawk_key, oath_signing_key} from '../assets/credentials' -const echo_username = 'firecamp'; -const echo_password = 'password'; -const hawk_id = 'dh37fgj492je'; - -const consumerKey = 'RKCGzna7bv9YD57c'; -const consumerSecret = 'D+EdQ-gs$-%@2Nu7'; +// const consumerKey = 'RKCGzna7bv9YD57c'; +; function buildSignatureBase(httpMethod, baseUrl, oauthParameters) { // Sort the OAuth parameters alphabetically by name @@ -276,7 +273,7 @@ export class RestController { } const credentials: Credentials = { - key: 'werxhqb98rpaxn39848xrunpaw3489ruxnpa98w4rxn', + key: hawk_key, algorithm: 'sha256', user: 'Steve', }; @@ -313,7 +310,6 @@ export class RestController { const parseOAuthHeader = (authorizationHeader) => { const oauthParams = {}; - // Regular expression to match OAuth parameters in the Authorization header const oauthRegex = /(\w+)="([^"]+)"/g; let match; @@ -345,10 +341,9 @@ export class RestController { const base_uri = `${req.protocol}://${req.get('Host')}${req.originalUrl}`; const signatureBase = buildSignatureBase('GET', base_uri, oauthParams); - const signingKey = 'D%2BEdQ-gs%24-%25%402Nu7&'; const expectedSignature = crypto - .createHmac('sha1', signingKey) + .createHmac('sha1', oath_signing_key) .update(signatureBase) .digest('base64'); @@ -366,7 +361,7 @@ export class RestController { base_uri, normalized_param_string, base_string: baseString, - signing_key: signingKey, + signing_key: oath_signing_key, }); } const status = 'pass'; From 2d1d80200f859171531fc1d93f51299634a84b08 Mon Sep 17 00:00:00 2001 From: nkBrew Date: Sun, 22 Oct 2023 16:19:48 -0600 Subject: [PATCH 13/19] minor fix for check spelling --- .../src/assets/credentials.js | 5 ++-- .../src/rest/rest.controller.ts | 30 +++++++++---------- 2 files changed, 18 insertions(+), 17 deletions(-) diff --git a/packages/firecamp-echo-server/src/assets/credentials.js b/packages/firecamp-echo-server/src/assets/credentials.js index 389953ff1..ae21001ba 100644 --- a/packages/firecamp-echo-server/src/assets/credentials.js +++ b/packages/firecamp-echo-server/src/assets/credentials.js @@ -2,5 +2,6 @@ export const echo_username = 'firecamp'; export const echo_password = 'password'; export const hawk_id = 'dh37fgj492je'; export const hawk_key = 'werxhqb98rpaxn39848xrunpaw3489ruxnpa98w4rxn'; -export const oath_signing_key = 'D%2BEdQ-gs%24-%25%402Nu7&' -const consumerSecret = 'D+EdQ-gs$-%@2Nu7' \ No newline at end of file +export const oath_signing_key = 'D%2BEdQ-gs%24-%25%402Nu7&'; +const consumerSecret = 'D+EdQ-gs$-%@2Nu7'; +const consumerKey = 'RKCGzna7bv9YD57c'; diff --git a/packages/firecamp-echo-server/src/rest/rest.controller.ts b/packages/firecamp-echo-server/src/rest/rest.controller.ts index 2c7b60d42..9ba202ed5 100644 --- a/packages/firecamp-echo-server/src/rest/rest.controller.ts +++ b/packages/firecamp-echo-server/src/rest/rest.controller.ts @@ -28,10 +28,13 @@ import { createReadStream } from 'fs'; import { join } from 'path'; import { createDeflate, createGzip } from 'zlib'; import { Readable } from 'stream'; -import {echo_username, echo_password, hawk_id, hawk_key, oath_signing_key} from '../assets/credentials' - -// const consumerKey = 'RKCGzna7bv9YD57c'; -; +import { + echo_username, + echo_password, + hawk_id, + hawk_key, + oath_signing_key, +} from '../assets/credentials'; function buildSignatureBase(httpMethod, baseUrl, oauthParameters) { // Sort the OAuth parameters alphabetically by name @@ -341,7 +344,6 @@ export class RestController { const base_uri = `${req.protocol}://${req.get('Host')}${req.originalUrl}`; const signatureBase = buildSignatureBase('GET', base_uri, oauthParams); - const expectedSignature = crypto .createHmac('sha1', oath_signing_key) .update(signatureBase) @@ -353,16 +355,14 @@ export class RestController { const normalized_param_string = consumerKey + '&' + nonce + '&' + signatureMethod + '&' + timestamp; const baseString = signatureBase; - return res - .status(401) - .json({ - status, - message, - base_uri, - normalized_param_string, - base_string: baseString, - signing_key: oath_signing_key, - }); + return res.status(401).json({ + status, + message, + base_uri, + normalized_param_string, + base_string: baseString, + signing_key: oath_signing_key, + }); } const status = 'pass'; const message = 'OAuth-1.0a signature verification was successful'; From 0fab3ac74138513eac78bd22adc3008bd3c3acc3 Mon Sep 17 00:00:00 2001 From: nkBrew Date: Sun, 22 Oct 2023 18:07:19 -0600 Subject: [PATCH 14/19] Deduplicate some code. CHange credentials to ts file --- .github/actions/spelling/excludes.txt | 2 +- packages/firecamp-echo-server/package.json | 1 - .../assets/{credentials.js => credentials.ts} | 0 .../src/rest/rest.controller.ts | 35 ++++++------------- .../src/utlities/restControllerUtilities.ts | 12 +++++++ 5 files changed, 24 insertions(+), 26 deletions(-) rename packages/firecamp-echo-server/src/assets/{credentials.js => credentials.ts} (100%) create mode 100644 packages/firecamp-echo-server/src/utlities/restControllerUtilities.ts diff --git a/.github/actions/spelling/excludes.txt b/.github/actions/spelling/excludes.txt index abe6c69c7..22cd8acee 100644 --- a/.github/actions/spelling/excludes.txt +++ b/.github/actions/spelling/excludes.txt @@ -90,5 +90,5 @@ ^\Qpnpm-lock.yaml\E$ ^\Qpackages/firecamp-echo-server/src/assets/unicodedemo.html\E$ ignore$ -^\Qpackages/firecamp-echo-server/src/assets/credentials.js\E$ +^\Qpackages/firecamp-echo-server/src/assets/credentials.ts\E$ ignore$ \ No newline at end of file diff --git a/packages/firecamp-echo-server/package.json b/packages/firecamp-echo-server/package.json index 31177df01..38494cff9 100644 --- a/packages/firecamp-echo-server/package.json +++ b/packages/firecamp-echo-server/package.json @@ -43,7 +43,6 @@ "@nestjs/cli": "9.0.0", "@nestjs/schematics": "9.0.1", "@nestjs/testing": "9.0.1", - "@types/compression": "^1.7.3", "@types/cookie-parser": "^1.4.4", "@types/express": "4.17.13", "@types/hawk": "^9.0.4", diff --git a/packages/firecamp-echo-server/src/assets/credentials.js b/packages/firecamp-echo-server/src/assets/credentials.ts similarity index 100% rename from packages/firecamp-echo-server/src/assets/credentials.js rename to packages/firecamp-echo-server/src/assets/credentials.ts diff --git a/packages/firecamp-echo-server/src/rest/rest.controller.ts b/packages/firecamp-echo-server/src/rest/rest.controller.ts index 9ba202ed5..2e0abfe88 100644 --- a/packages/firecamp-echo-server/src/rest/rest.controller.ts +++ b/packages/firecamp-echo-server/src/rest/rest.controller.ts @@ -34,7 +34,8 @@ import { hawk_id, hawk_key, oath_signing_key, -} from '../assets/credentials'; +} from 'src/assets/credentials'; +import { parseAuthHeader } from 'src/utlities/restControllerUtilities'; function buildSignatureBase(httpMethod, baseUrl, oauthParameters) { // Sort the OAuth parameters alphabetically by name @@ -210,7 +211,7 @@ export class RestController { // Digest Auth @Get('digest-auth') - digestAuth(@Req() req, @Headers() headers, @Response() res) { + digestAuth(@Headers() headers, @Response() res) { const realm = 'Users'; if (!('authorization' in headers)) { @@ -229,13 +230,7 @@ export class RestController { return res.status(401).send('Unauthorized'); } - const authArgs = headers.authorization.slice(7).split(', '); - const argsMap = {}; - - authArgs.forEach((arg) => { - const split = arg.replaceAll('"', '').replace('=', '-:-').split('-:-'); - argsMap[split[0]] = split[1]; - }); + const argsMap = parseAuthHeader(headers.authorization.slice(7)) if ( !( @@ -245,8 +240,13 @@ export class RestController { argsMap['response'] ) ) { - return res.json({ authorized: false }); + return res.status(401).send('Unauthorized'); + } + + if (argsMap['username'] !== echo_username){ + return res.status(401).send('Unauthorized'); } + const HA1 = crypto .createHash('md5') .update(`${argsMap['username']}:${argsMap['realm']}:${echo_password}`) @@ -310,19 +310,6 @@ export class RestController { // OAuth 1.0 @Get('oauth1') OAuth1(@Req() req, @Headers() headers, @Response() res) { - const parseOAuthHeader = (authorizationHeader) => { - const oauthParams = {}; - - const oauthRegex = /(\w+)="([^"]+)"/g; - - let match; - while ((match = oauthRegex.exec(authorizationHeader)) !== null) { - oauthParams[match[1]] = match[2]; - } - - return oauthParams; - }; - if (!('authorization' in headers)) { return res.status(401).send('Unauthorized'); } @@ -333,7 +320,7 @@ export class RestController { return res.status(401).send('Unauthorized'); } - const oauthParams = parseOAuthHeader(authorizationHeader.slice(6)); + const oauthParams = parseAuthHeader(authorizationHeader.slice(6)); const consumerKey = oauthParams['oauth_consumer_key']; const timestamp = oauthParams['oauth_timestamp']; diff --git a/packages/firecamp-echo-server/src/utlities/restControllerUtilities.ts b/packages/firecamp-echo-server/src/utlities/restControllerUtilities.ts new file mode 100644 index 000000000..447b119fc --- /dev/null +++ b/packages/firecamp-echo-server/src/utlities/restControllerUtilities.ts @@ -0,0 +1,12 @@ +export const parseAuthHeader = (authorizationHeader) => { + const oauthParams = {}; + + const oauthRegex = /(\w+)="([^"]+)"/g; + + let match; + while ((match = oauthRegex.exec(authorizationHeader)) !== null) { + oauthParams[match[1]] = match[2]; + } + + return oauthParams; +}; \ No newline at end of file From 17dd6653798b083f350169b2bfac2c2e7c39a75f Mon Sep 17 00:00:00 2001 From: nkBrew Date: Sun, 22 Oct 2023 18:37:12 -0600 Subject: [PATCH 15/19] Fixed spelling error --- packages/firecamp-echo-server/src/rest/rest.controller.ts | 2 +- .../src/{utlities => utilities}/restControllerUtilities.ts | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename packages/firecamp-echo-server/src/{utlities => utilities}/restControllerUtilities.ts (100%) diff --git a/packages/firecamp-echo-server/src/rest/rest.controller.ts b/packages/firecamp-echo-server/src/rest/rest.controller.ts index 2e0abfe88..24d2c543a 100644 --- a/packages/firecamp-echo-server/src/rest/rest.controller.ts +++ b/packages/firecamp-echo-server/src/rest/rest.controller.ts @@ -35,7 +35,7 @@ import { hawk_key, oath_signing_key, } from 'src/assets/credentials'; -import { parseAuthHeader } from 'src/utlities/restControllerUtilities'; +import { parseAuthHeader } from 'src/utilities/restControllerUtilities'; function buildSignatureBase(httpMethod, baseUrl, oauthParameters) { // Sort the OAuth parameters alphabetically by name diff --git a/packages/firecamp-echo-server/src/utlities/restControllerUtilities.ts b/packages/firecamp-echo-server/src/utilities/restControllerUtilities.ts similarity index 100% rename from packages/firecamp-echo-server/src/utlities/restControllerUtilities.ts rename to packages/firecamp-echo-server/src/utilities/restControllerUtilities.ts From 50a2c6b57ac6f63a42b32564f511bc17a7ac716f Mon Sep 17 00:00:00 2001 From: nkBrew Date: Sun, 22 Oct 2023 18:42:42 -0600 Subject: [PATCH 16/19] Cleanup --- .../src/assets/credentials.ts | 2 -- .../src/rest/rest.controller.ts | 26 ++----------------- .../src/utilities/restControllerUtilities.ts | 24 ++++++++++++++++- 3 files changed, 25 insertions(+), 27 deletions(-) diff --git a/packages/firecamp-echo-server/src/assets/credentials.ts b/packages/firecamp-echo-server/src/assets/credentials.ts index ae21001ba..33118e15d 100644 --- a/packages/firecamp-echo-server/src/assets/credentials.ts +++ b/packages/firecamp-echo-server/src/assets/credentials.ts @@ -3,5 +3,3 @@ export const echo_password = 'password'; export const hawk_id = 'dh37fgj492je'; export const hawk_key = 'werxhqb98rpaxn39848xrunpaw3489ruxnpa98w4rxn'; export const oath_signing_key = 'D%2BEdQ-gs%24-%25%402Nu7&'; -const consumerSecret = 'D+EdQ-gs$-%@2Nu7'; -const consumerKey = 'RKCGzna7bv9YD57c'; diff --git a/packages/firecamp-echo-server/src/rest/rest.controller.ts b/packages/firecamp-echo-server/src/rest/rest.controller.ts index 24d2c543a..2ea1d57fb 100644 --- a/packages/firecamp-echo-server/src/rest/rest.controller.ts +++ b/packages/firecamp-echo-server/src/rest/rest.controller.ts @@ -35,29 +35,7 @@ import { hawk_key, oath_signing_key, } from 'src/assets/credentials'; -import { parseAuthHeader } from 'src/utilities/restControllerUtilities'; - -function buildSignatureBase(httpMethod, baseUrl, oauthParameters) { - // Sort the OAuth parameters alphabetically by name - const sortedParameters = Object.keys(oauthParameters) - .sort() - .filter((key) => key !== 'oauth_signature') - .map((key) => key + '=' + encodeURIComponent(oauthParameters[key])); - - // Create the parameter string by joining the sorted parameters with "&" - const parameterString = sortedParameters.join('&'); - - // Encode the HTTP method and base URL - const encodedHttpMethod = encodeURIComponent(httpMethod); - const encodedBaseUrl = encodeURIComponent(baseUrl); - - // Construct the signature base string - const signatureBase = `${encodedHttpMethod}&${encodedBaseUrl}&${encodeURIComponent( - parameterString - )}`; - - return signatureBase; -} +import { buildOauthSignatureBase, parseAuthHeader } from 'src/utilities/restControllerUtilities'; @Controller('') export class RestController { @@ -329,7 +307,7 @@ export class RestController { const signatureMethod = oauthParams['oauth_signature_method']; const base_uri = `${req.protocol}://${req.get('Host')}${req.originalUrl}`; - const signatureBase = buildSignatureBase('GET', base_uri, oauthParams); + const signatureBase = buildOauthSignatureBase('GET', base_uri, oauthParams); const expectedSignature = crypto .createHmac('sha1', oath_signing_key) diff --git a/packages/firecamp-echo-server/src/utilities/restControllerUtilities.ts b/packages/firecamp-echo-server/src/utilities/restControllerUtilities.ts index 447b119fc..2c2d57c39 100644 --- a/packages/firecamp-echo-server/src/utilities/restControllerUtilities.ts +++ b/packages/firecamp-echo-server/src/utilities/restControllerUtilities.ts @@ -9,4 +9,26 @@ export const parseAuthHeader = (authorizationHeader) => { } return oauthParams; -}; \ No newline at end of file +}; + +export const buildOauthSignatureBase = (httpMethod, baseUrl, oauthParameters) => { + // Sort the OAuth parameters alphabetically by name + const sortedParameters = Object.keys(oauthParameters) + .sort() + .filter((key) => key !== 'oauth_signature') + .map((key) => key + '=' + encodeURIComponent(oauthParameters[key])); + + // Create the parameter string by joining the sorted parameters with "&" + const parameterString = sortedParameters.join('&'); + + // Encode the HTTP method and base URL + const encodedHttpMethod = encodeURIComponent(httpMethod); + const encodedBaseUrl = encodeURIComponent(baseUrl); + + // Construct the signature base string + const signatureBase = `${encodedHttpMethod}&${encodedBaseUrl}&${encodeURIComponent( + parameterString + )}`; + + return signatureBase; +} \ No newline at end of file From 8eab1985c057acc49e590b3b151df7052ec25ae4 Mon Sep 17 00:00:00 2001 From: Nishchit Dhanani Date: Wed, 1 Nov 2023 18:41:52 +0530 Subject: [PATCH 17/19] chore: cors enabled for * pattern --- packages/firecamp-echo-server/src/main.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/packages/firecamp-echo-server/src/main.ts b/packages/firecamp-echo-server/src/main.ts index fd10a6018..8166ffe0e 100644 --- a/packages/firecamp-echo-server/src/main.ts +++ b/packages/firecamp-echo-server/src/main.ts @@ -1,19 +1,21 @@ import { NestFactory } from '@nestjs/core'; -import { WsAdapter, } from '@nestjs/platform-ws'; +import { WsAdapter } from '@nestjs/platform-ws'; // import { IoAdapter } from '@nestjs/platform-socket.io'; import { AppModule } from './app.module'; import * as cookieParser from 'cookie-parser'; async function bootstrap() { const app = await NestFactory.create(AppModule); - app.enableCors(); + app.enableCors({ + origin: '*', + }); app.useWebSocketAdapter(new WsAdapter(app)); // app.useWebSocketAdapter(new IoAdapter(app)); - app.use(cookieParser()) + app.use(cookieParser()); await app.listen(3000); console.log(`Application is running on: ${await app.getUrl()}`); - app.getUrl().then(url => console.log(url)) + app.getUrl().then((url) => console.log(url)); } bootstrap(); From ff58bd7ced42273878fa9c0ae12785cfb7032aef Mon Sep 17 00:00:00 2001 From: nkBrew Date: Sun, 12 Nov 2023 14:07:01 -0600 Subject: [PATCH 18/19] EchoServer - added time api --- packages/firecamp-echo-server/package.json | 1 + .../src/rest/rest.controller.ts | 332 +++++++++++++++++- 2 files changed, 315 insertions(+), 18 deletions(-) diff --git a/packages/firecamp-echo-server/package.json b/packages/firecamp-echo-server/package.json index 38494cff9..b9583192f 100644 --- a/packages/firecamp-echo-server/package.json +++ b/packages/firecamp-echo-server/package.json @@ -29,6 +29,7 @@ "class-validator": "0.14.0", "cookie-parser": "^1.4.6", "hawk": "^9.0.1", + "moment": "^2.29.4", "raw-body": "^2.5.2", "reflect-metadata": "0.1.13", "rimraf": "3.0.2", diff --git a/packages/firecamp-echo-server/src/rest/rest.controller.ts b/packages/firecamp-echo-server/src/rest/rest.controller.ts index 2ea1d57fb..3f234dd7c 100644 --- a/packages/firecamp-echo-server/src/rest/rest.controller.ts +++ b/packages/firecamp-echo-server/src/rest/rest.controller.ts @@ -19,6 +19,7 @@ import { Ip, Header, StreamableFile, + Res, } from '@nestjs/common'; import * as crypto from 'crypto'; import { response } from 'express'; @@ -35,7 +36,13 @@ import { hawk_key, oath_signing_key, } from 'src/assets/credentials'; -import { buildOauthSignatureBase, parseAuthHeader } from 'src/utilities/restControllerUtilities'; +import { + buildOauthSignatureBase, + parseAuthHeader, +} from 'src/utilities/restControllerUtilities'; +import { get } from 'http'; +import * as moment from 'moment'; + @Controller('') export class RestController { @@ -208,7 +215,7 @@ export class RestController { return res.status(401).send('Unauthorized'); } - const argsMap = parseAuthHeader(headers.authorization.slice(7)) + const argsMap = parseAuthHeader(headers.authorization.slice(7)); if ( !( @@ -218,11 +225,11 @@ export class RestController { argsMap['response'] ) ) { - return res.status(401).send('Unauthorized'); + return res.status(401).send('Unauthorized'); } - if (argsMap['username'] !== echo_username){ - return res.status(401).send('Unauthorized'); + if (argsMap['username'] !== echo_username) { + return res.status(401).send('Unauthorized'); } const HA1 = crypto @@ -484,20 +491,309 @@ export class RestController { return { ip: ip }; } - /** Utilities / Date and Time */ - // 1. Current UTC time - // 2. Timestamp validity - // 3. Format timestamp - // 4. Extract timestamp unit - // 5. Time addition - // 6. Time subtraction - // 7. Start of time - // 8. Object representation - // 9. Before comparisons - // 10. After comparisons - // 11. Between timestamps - // 12. Leap year check + // Date and Time + // Current UTC time + @Get('time/now') + timeNow() { + return new Date().toUTCString(); + } + + // Timestamp validity + @Get('time/valid') + timeValid(@Query() queryParams, @Response() res) { + const { timestamp, locale, format } = queryParams; + const strict = queryParams['strict'] + ? queryParams['strict'].toLowerCase() === 'true' + : false; + + const parsed = moment(timestamp, format, locale, strict); + + return res.json({ valid: parsed.isValid() }); + } + + // Format timestamp + @Get('time/format') + timeFormat(@Query() queryParams, @Response() res) { + const { timestamp, locale, format } = queryParams; + if (!timestamp) { + return res + .status(400) + .send('Invalid undefined in `timestamp` query param'); + } + + const strict = queryParams['strict'] + ? queryParams['strict'].toLowerCase() === 'true' + : false; + + const parsed = moment(timestamp, format, locale, strict).format(format); + + console.log(parsed); + return res.json({ format: parsed }); + } + + // Extract timestamp unit + @Get('time/extract') + timeExtract(@Query() queryParams, @Response() res) { + const { locale, format } = queryParams; + const timestamp = queryParams.timestamp || new Date().toUTCString(); + const unit = queryParams.unit && queryParams.unit.toLowerCase() || 'year' + const strict = queryParams['strict'] + ? queryParams['strict'].toLowerCase() === 'true' + : false; + + const parsed = moment(timestamp, locale, format, strict) + const obj = { + years: parsed.year(), + months: parsed.month(), + date: parsed.date(), + hours: parsed.hour(), + minutes: parsed.minute(), + seconds: parsed.second(), + milliseconds: parsed.millisecond(), + }; + + return res.json({unit: obj[unit]}) + + } + + // Time addition + @Get('time/add') + timeAddition(@Query() queryParams, @Response() res) { + const { timestamp, locale, format } = queryParams; + if (!timestamp) { + return res + .status(400) + .send('Invalid undefined in `timestamp` query param'); + } + + const strict = queryParams['strict'] + ? queryParams['strict'].toLowerCase() === 'true' + : false; + + const { + years, + days, + months, + quarters, + weeks, + hours, + minutes, + seconds, + milliseconds, + } = queryParams; + + const additions = [ + { unit: 'year', amount: years }, + { unit: 'day', amount: days }, + { unit: 'month', amount: months }, + { unit: 'quarter', amount: quarters }, + { unit: 'week', amount: weeks }, + { unit: 'hour', amount: hours }, + { unit: 'minute', amount: minutes }, + { unit: 'second', amount: seconds }, + { unit: 'millisecond', amount: milliseconds }, + ]; + + const parsed = moment(timestamp, format, locale, strict); + + additions.forEach((addition) => { + if (!addition.amount) { + return; + } + parsed; + parsed.add(addition.amount, addition.unit); + }); + + console.log(parsed); + return res.json({ sum: parsed }); + } + // Time subtraction + @Get('time/subtract') + timeSubtraction(@Query() queryParams, @Response() res) { + const { timestamp, locale, format } = queryParams; + + if (!timestamp) { + return res + .status(400) + .send('Invalid undefined in `timestamp` query param'); + } + + const strict = queryParams['strict'] + ? queryParams['strict'].toLowerCase() === 'true' + : false; + + const { + years, + days, + months, + quarters, + weeks, + hours, + minutes, + seconds, + milliseconds, + } = queryParams; + + const subtractions = [ + { unit: 'years', amount: years }, + { unit: 'days', amount: days }, + { unit: 'months', amount: months }, + { unit: 'quarters', amount: quarters }, + { unit: 'weeks', amount: weeks }, + { unit: 'hours', amount: hours }, + { unit: 'minutes', amount: minutes }, + { unit: 'seconds', amount: seconds }, + { unit: 'milliseconds', amount: milliseconds }, + ]; + + const parsed = moment(timestamp, format, locale, strict); + + subtractions.forEach((subtraction) => { + if (!subtraction.amount) { + return; + } + parsed.subtract(subtraction.amount, subtraction.unit); + }); + + return res.json({ difference: parsed }); + } + + // Start of time + @Get('time/start') + startOf(@Query() queryParams, @Response() res) { + const { timestamp, locale, format, unit } = queryParams; + + if (!timestamp) { + return res + .status(400) + .send('Invalid undefined in `timestamp` query param'); + } + + const strict = queryParams['strict'] + ? queryParams['strict'].toLowerCase() === 'true' + : false; + + const parsed = moment(timestamp, format, locale, strict); + + parsed.startOf(unit).format(); + + return res.json({ start: parsed.startOf(unit).format() }); + } + + // Object representation + @Get('time/object') + objectRepresentation(@Query() queryParams, @Response() res) { + const { timestamp, locale, format } = queryParams; + + if (!timestamp) { + return res + .status(400) + .send('Invalid undefined in `timestamp` query param'); + } + + const strict = queryParams['strict'] + ? queryParams['strict'].toLowerCase() === 'true' + : false; + const parsed = moment(timestamp, format, locale, strict); + const obj = { + years: parsed.year(), + months: parsed.month(), + date: parsed.date(), + hours: parsed.hour(), + minutes: parsed.minute(), + seconds: parsed.second(), + milliseconds: parsed.millisecond(), + }; + + console.log(obj); + + return res.json(obj); + } + // Before comparisons + @Get('time/before') + timeBefore(@Query() queryParams, @Response() res) { + const { timestamp, locale, format, target } = queryParams; + if (!timestamp) { + return res + .status(400) + .send('Invalid undefined in `timestamp` query param'); + } + if (!target) { + return res.status(400).send('Invalid undefined in `target` query param'); + } + const strict = queryParams['strict'] + ? queryParams['strict'].toLowerCase() === 'true' + : false; + const parsed = moment(timestamp, format, locale, strict); + const parsedTarget = moment(target, format, locale, strict); + return res.json({ before: parsed.isBefore(parsedTarget) }); + } + // After comparisons + @Get('time/after') + timeAfter(@Query() queryParams, @Response() res) { + const { timestamp, locale, format, target } = queryParams; + if (!timestamp) { + return res + .status(400) + .send('Invalid undefined in `timestamp` query param'); + } + if (!target) { + return res.status(400).send('Invalid undefined in `target` query param'); + } + const strict = queryParams['strict'] + ? queryParams['strict'].toLowerCase() === 'true' + : false; + const parsed = moment(timestamp, format, locale, strict); + const parsedTarget = moment(target, format, locale, strict); + + return res.json({ after: parsed.isAfter(parsedTarget) }); + } + + // Between timestamps + @Get('time/between') + timeBetween(@Query() queryParams, @Response() res) { + const { timestamp, locale, format, start, end } = queryParams; + if (!timestamp) { + return res + .status(400) + .send('Invalid undefined in `timestamp` query param'); + } + if (!start) { + return res.status(400).send('Invalid undefined in `start` query param'); + } + if (!end) { + return res.status(400).send('Invalid undefined in `end` query param'); + } + const strict = queryParams['strict'] + ? queryParams['strict'].toLowerCase() === 'true' + : false; + const parsed = moment(timestamp, format, locale, strict); + const parsedStart = moment(start, format, locale, strict); + const parsedEnd = moment(end, format, locale, strict); + + return res.json({ + between: parsed.isAfter(parsedStart) && parsed.isBefore(parsedEnd), + }); + } + + // Leap year check + @Get('time/leap') + leap(@Query() queryParams, @Response() res) { + const { timestamp, locale, format } = queryParams; + if (!timestamp) { + return res + .status(400) + .send('Invalid undefined in `timestamp` query param'); + } + + const strict = queryParams['strict'] + ? queryParams['strict'].toLowerCase() === 'true' + : false; + const parsed = moment(timestamp, format, locale, strict); + + return res.json({ leap: parsed.isLeapYear() }); + } /** Auth: Digest */ // 1. DigestAuth Request } From 2901fafccf6ff495f948575b0fc3e450751e8a4c Mon Sep 17 00:00:00 2001 From: nkBrew Date: Sun, 12 Nov 2023 14:16:13 -0600 Subject: [PATCH 19/19] EchoServer - Camelcase --- .../src/assets/credentials.ts | 10 ++++---- .../src/rest/rest.controller.ts | 24 +++++++++---------- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/packages/firecamp-echo-server/src/assets/credentials.ts b/packages/firecamp-echo-server/src/assets/credentials.ts index 33118e15d..4e0fd7e14 100644 --- a/packages/firecamp-echo-server/src/assets/credentials.ts +++ b/packages/firecamp-echo-server/src/assets/credentials.ts @@ -1,5 +1,5 @@ -export const echo_username = 'firecamp'; -export const echo_password = 'password'; -export const hawk_id = 'dh37fgj492je'; -export const hawk_key = 'werxhqb98rpaxn39848xrunpaw3489ruxnpa98w4rxn'; -export const oath_signing_key = 'D%2BEdQ-gs%24-%25%402Nu7&'; +export const echoUsername = 'firecamp'; +export const echoPassword = 'password'; +export const hawkId = 'dh37fgj492je'; +export const hawkKey = 'werxhqb98rpaxn39848xrunpaw3489ruxnpa98w4rxn'; +export const oathSigningKey = 'D%2BEdQ-gs%24-%25%402Nu7&'; diff --git a/packages/firecamp-echo-server/src/rest/rest.controller.ts b/packages/firecamp-echo-server/src/rest/rest.controller.ts index 3f234dd7c..5b4b1a92d 100644 --- a/packages/firecamp-echo-server/src/rest/rest.controller.ts +++ b/packages/firecamp-echo-server/src/rest/rest.controller.ts @@ -30,11 +30,11 @@ import { join } from 'path'; import { createDeflate, createGzip } from 'zlib'; import { Readable } from 'stream'; import { - echo_username, - echo_password, - hawk_id, - hawk_key, - oath_signing_key, + echoUsername, + echoPassword, + hawkId, + hawkKey, + oathSigningKey, } from 'src/assets/credentials'; import { buildOauthSignatureBase, @@ -187,7 +187,7 @@ export class RestController { const username = decodedSplit[0]; const password = decodedSplit[1]; - if (username !== echo_username || password !== echo_password) { + if (username !== echoUsername || password !== echoPassword) { return res.status(401).send('Unauthorized'); } @@ -228,13 +228,13 @@ export class RestController { return res.status(401).send('Unauthorized'); } - if (argsMap['username'] !== echo_username) { + if (argsMap['username'] !== echoUsername) { return res.status(401).send('Unauthorized'); } const HA1 = crypto .createHash('md5') - .update(`${argsMap['username']}:${argsMap['realm']}:${echo_password}`) + .update(`${argsMap['username']}:${argsMap['realm']}:${echoPassword}`) .digest('hex'); const HA2 = crypto .createHash('md5') @@ -256,12 +256,12 @@ export class RestController { @Get('hawk-auth') async hawkAuth(@Req() req, @Response() res) { const credentialsFunc = function (id) { - if (id !== hawk_id) { + if (id !== hawkId) { return undefined; } const credentials: Credentials = { - key: hawk_key, + key: hawkKey, algorithm: 'sha256', user: 'Steve', }; @@ -317,7 +317,7 @@ export class RestController { const signatureBase = buildOauthSignatureBase('GET', base_uri, oauthParams); const expectedSignature = crypto - .createHmac('sha1', oath_signing_key) + .createHmac('sha1', oathSigningKey) .update(signatureBase) .digest('base64'); @@ -333,7 +333,7 @@ export class RestController { base_uri, normalized_param_string, base_string: baseString, - signing_key: oath_signing_key, + signing_key: oathSigningKey, }); } const status = 'pass';