Skip to content

Commit

Permalink
Simplify profile auth & signature flow (#2012)
Browse files Browse the repository at this point in the history
* Remove JWT logic from api

* add tests

* use validateSignature in handlers

* npm version major

* use wallet regex

* cleanup put schema and add tests

* add FirestoreUserDoc schema and add more tests

* merge firebase tools and cleanup

* fix: undefined nonce

* remove session plugin

* fix type definitions

* abstract authenticateProfile prehandler

* Add fallback for SIGN_PROFILE_MESSAGE

* push test debug

* resolve conflicts & rebase staging

* fix import

* pass tests and remove debug

* add better fallback

* generate:types

* correct status code in authenticate middleware

* add debug statements

* push more debug

* remove debug and change log

* fix: empty strings instead of undefined

* fix tests
  • Loading branch information
Atmosfearful authored Jan 30, 2024
1 parent 506014c commit 62f4b34
Show file tree
Hide file tree
Showing 41 changed files with 860 additions and 774 deletions.
5 changes: 1 addition & 4 deletions carbonmark-api/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@klimadao/carbonmark-api",
"version": "5.3.6",
"version": "6.0.0",
"description": "An API for exploring Carbonmark project data, prices and activity.",
"main": "app.ts",
"scripts": {
Expand All @@ -17,16 +17,13 @@
"license": "ISC",
"dependencies": {
"@fastify/autoload": "^5.0.0",
"@fastify/cookie": "^8.3.0",
"@fastify/cors": "^8.3.0",
"@fastify/jwt": "^6.7.1",
"@fastify/rate-limit": "^8.0.1",
"@fastify/response-validation": "^2.3.1",
"@fastify/sensible": "^5.0.0",
"@fastify/session": "^10.3.0",
"@fastify/swagger": "^8.8.0",
"@fastify/type-provider-typebox": "^3.1.0",
"@mgcrea/fastify-session": "^1.1.0",
"@sanity/client": "^6.1.2",
"@sinclair/typebox": "^0.28.5",
"dotenv": "^16.1.4",
Expand Down
84 changes: 0 additions & 84 deletions carbonmark-api/postman_collection.json
Original file line number Diff line number Diff line change
Expand Up @@ -604,48 +604,6 @@
},
"response": []
},
{
"name": "loginUser",
"event": [
{
"listen": "prerequest",
"script": {
"exec": [""],
"type": "text/javascript"
}
},
{
"listen": "test",
"script": {
"exec": [
"if (pm.environment.get(\"SKIP_MUTATIONS\") === \"true\") {",
" postman.setNextRequest(null);",
"}"
],
"type": "text/javascript"
}
}
],
"request": {
"method": "POST",
"header": [],
"body": {
"mode": "raw",
"raw": "{\n \"wallet\":\"{{wallet}}\"\n}",
"options": {
"raw": {
"language": "json"
}
}
},
"url": {
"raw": "{{url}}/users/login",
"host": ["{{url}}"],
"path": ["users", "login"]
}
},
"response": []
},
{
"name": "getListingById",
"request": {
Expand All @@ -661,48 +619,6 @@
}
},
"response": []
},
{
"name": "verifyUser",
"event": [
{
"listen": "prerequest",
"script": {
"exec": [""],
"type": "text/javascript"
}
},
{
"listen": "test",
"script": {
"exec": [
"if (pm.environment.get(\"SKIP_MUTATIONS\") === \"true\") {",
" postman.setNextRequest(null);",
"}"
],
"type": "text/javascript"
}
}
],
"request": {
"method": "POST",
"header": [],
"body": {
"mode": "raw",
"raw": "{\n \"wallet\":\"{{wallet}}\",\n \"signature\":\"{{signature}}\"\n}",
"options": {
"raw": {
"language": "json"
}
}
},
"url": {
"raw": "{{url}}/users/login/verify",
"host": ["{{url}}"],
"path": ["users", "login", "verify"]
}
},
"response": []
}
],
"event": [
Expand Down
22 changes: 22 additions & 0 deletions carbonmark-api/src/.generated/mocks/digitalCarbon.mocks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2224,6 +2224,7 @@ export const aRetire = (overrides?: Partial<Retire>, _relationshipsToOmit: Set<s
id: overrides && overrides.hasOwnProperty('id') ? overrides.id! : 'id',
klimaRetire: overrides && overrides.hasOwnProperty('klimaRetire') ? overrides.klimaRetire! : relationshipsToOmit.has('KlimaRetire') ? {} as KlimaRetire : aKlimaRetire({}, relationshipsToOmit),
pool: overrides && overrides.hasOwnProperty('pool') ? overrides.pool! : relationshipsToOmit.has('CarbonPool') ? {} as CarbonPool : aCarbonPool({}, relationshipsToOmit),
provenance: overrides && overrides.hasOwnProperty('provenance') ? overrides.provenance! : relationshipsToOmit.has('ProvenanceRecord') ? {} as ProvenanceRecord : aProvenanceRecord({}, relationshipsToOmit),
retirementMessage: overrides && overrides.hasOwnProperty('retirementMessage') ? overrides.retirementMessage! : 'nostrum',
retiringAddress: overrides && overrides.hasOwnProperty('retiringAddress') ? overrides.retiringAddress! : relationshipsToOmit.has('Account') ? {} as Account : anAccount({}, relationshipsToOmit),
retiringName: overrides && overrides.hasOwnProperty('retiringName') ? overrides.retiringName! : 'est',
Expand Down Expand Up @@ -2371,6 +2372,27 @@ export const aRetire_Filter = (overrides?: Partial<Retire_Filter>, _relationship
pool_not_starts_with_nocase: overrides && overrides.hasOwnProperty('pool_not_starts_with_nocase') ? overrides.pool_not_starts_with_nocase! : 'ratione',
pool_starts_with: overrides && overrides.hasOwnProperty('pool_starts_with') ? overrides.pool_starts_with! : 'dignissimos',
pool_starts_with_nocase: overrides && overrides.hasOwnProperty('pool_starts_with_nocase') ? overrides.pool_starts_with_nocase! : 'ratione',
provenance: overrides && overrides.hasOwnProperty('provenance') ? overrides.provenance! : 'ut',
provenance_: overrides && overrides.hasOwnProperty('provenance_') ? overrides.provenance_! : relationshipsToOmit.has('ProvenanceRecord_Filter') ? {} as ProvenanceRecord_Filter : aProvenanceRecord_Filter({}, relationshipsToOmit),
provenance_contains: overrides && overrides.hasOwnProperty('provenance_contains') ? overrides.provenance_contains! : 'sint',
provenance_contains_nocase: overrides && overrides.hasOwnProperty('provenance_contains_nocase') ? overrides.provenance_contains_nocase! : 'qui',
provenance_ends_with: overrides && overrides.hasOwnProperty('provenance_ends_with') ? overrides.provenance_ends_with! : 'nihil',
provenance_ends_with_nocase: overrides && overrides.hasOwnProperty('provenance_ends_with_nocase') ? overrides.provenance_ends_with_nocase! : 'voluptatum',
provenance_gt: overrides && overrides.hasOwnProperty('provenance_gt') ? overrides.provenance_gt! : 'doloremque',
provenance_gte: overrides && overrides.hasOwnProperty('provenance_gte') ? overrides.provenance_gte! : 'dolorum',
provenance_in: overrides && overrides.hasOwnProperty('provenance_in') ? overrides.provenance_in! : ['laudantium'],
provenance_lt: overrides && overrides.hasOwnProperty('provenance_lt') ? overrides.provenance_lt! : 'cum',
provenance_lte: overrides && overrides.hasOwnProperty('provenance_lte') ? overrides.provenance_lte! : 'modi',
provenance_not: overrides && overrides.hasOwnProperty('provenance_not') ? overrides.provenance_not! : 'ut',
provenance_not_contains: overrides && overrides.hasOwnProperty('provenance_not_contains') ? overrides.provenance_not_contains! : 'quidem',
provenance_not_contains_nocase: overrides && overrides.hasOwnProperty('provenance_not_contains_nocase') ? overrides.provenance_not_contains_nocase! : 'et',
provenance_not_ends_with: overrides && overrides.hasOwnProperty('provenance_not_ends_with') ? overrides.provenance_not_ends_with! : 'eum',
provenance_not_ends_with_nocase: overrides && overrides.hasOwnProperty('provenance_not_ends_with_nocase') ? overrides.provenance_not_ends_with_nocase! : 'nulla',
provenance_not_in: overrides && overrides.hasOwnProperty('provenance_not_in') ? overrides.provenance_not_in! : ['sunt'],
provenance_not_starts_with: overrides && overrides.hasOwnProperty('provenance_not_starts_with') ? overrides.provenance_not_starts_with! : 'omnis',
provenance_not_starts_with_nocase: overrides && overrides.hasOwnProperty('provenance_not_starts_with_nocase') ? overrides.provenance_not_starts_with_nocase! : 'nostrum',
provenance_starts_with: overrides && overrides.hasOwnProperty('provenance_starts_with') ? overrides.provenance_starts_with! : 'recusandae',
provenance_starts_with_nocase: overrides && overrides.hasOwnProperty('provenance_starts_with_nocase') ? overrides.provenance_starts_with_nocase! : 'dignissimos',
retirementMessage: overrides && overrides.hasOwnProperty('retirementMessage') ? overrides.retirementMessage! : 'hic',
retirementMessage_contains: overrides && overrides.hasOwnProperty('retirementMessage_contains') ? overrides.retirementMessage_contains! : 'ipsa',
retirementMessage_contains_nocase: overrides && overrides.hasOwnProperty('retirementMessage_contains_nocase') ? overrides.retirementMessage_contains_nocase! : 'excepturi',
Expand Down
35 changes: 35 additions & 0 deletions carbonmark-api/src/.generated/types/digitalCarbon.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3349,6 +3349,8 @@ export type Retire = {
klimaRetire: Maybe<KlimaRetire>;
/** Pool credit was sourced from, if any */
pool: Maybe<CarbonPool>;
/** Final provenance record created by this retirement */
provenance: Maybe<ProvenanceRecord>;
/** Specific retirement message */
retirementMessage: Scalars['String'];
/** Retiree address */
Expand Down Expand Up @@ -3503,6 +3505,27 @@ export type Retire_Filter = {
pool_not_starts_with_nocase: InputMaybe<Scalars['String']>;
pool_starts_with: InputMaybe<Scalars['String']>;
pool_starts_with_nocase: InputMaybe<Scalars['String']>;
provenance: InputMaybe<Scalars['String']>;
provenance_: InputMaybe<ProvenanceRecord_Filter>;
provenance_contains: InputMaybe<Scalars['String']>;
provenance_contains_nocase: InputMaybe<Scalars['String']>;
provenance_ends_with: InputMaybe<Scalars['String']>;
provenance_ends_with_nocase: InputMaybe<Scalars['String']>;
provenance_gt: InputMaybe<Scalars['String']>;
provenance_gte: InputMaybe<Scalars['String']>;
provenance_in: InputMaybe<Array<Scalars['String']>>;
provenance_lt: InputMaybe<Scalars['String']>;
provenance_lte: InputMaybe<Scalars['String']>;
provenance_not: InputMaybe<Scalars['String']>;
provenance_not_contains: InputMaybe<Scalars['String']>;
provenance_not_contains_nocase: InputMaybe<Scalars['String']>;
provenance_not_ends_with: InputMaybe<Scalars['String']>;
provenance_not_ends_with_nocase: InputMaybe<Scalars['String']>;
provenance_not_in: InputMaybe<Array<Scalars['String']>>;
provenance_not_starts_with: InputMaybe<Scalars['String']>;
provenance_not_starts_with_nocase: InputMaybe<Scalars['String']>;
provenance_starts_with: InputMaybe<Scalars['String']>;
provenance_starts_with_nocase: InputMaybe<Scalars['String']>;
retirementMessage: InputMaybe<Scalars['String']>;
retirementMessage_contains: InputMaybe<Scalars['String']>;
retirementMessage_contains_nocase: InputMaybe<Scalars['String']>;
Expand Down Expand Up @@ -3614,6 +3637,18 @@ export enum Retire_OrderBy {
pool__name = 'pool__name',
pool__nextSnapshotDayID = 'pool__nextSnapshotDayID',
pool__supply = 'pool__supply',
provenance = 'provenance',
provenance__createdAt = 'provenance__createdAt',
provenance__id = 'provenance__id',
provenance__originalAmount = 'provenance__originalAmount',
provenance__receiver = 'provenance__receiver',
provenance__remainingAmount = 'provenance__remainingAmount',
provenance__sender = 'provenance__sender',
provenance__token = 'provenance__token',
provenance__tokenId = 'provenance__tokenId',
provenance__transactionHash = 'provenance__transactionHash',
provenance__transactionType = 'provenance__transactionType',
provenance__updatedAt = 'provenance__updatedAt',
retirementMessage = 'retirementMessage',
retiringAddress = 'retiringAddress',
retiringAddress__id = 'retiringAddress__id',
Expand Down
6 changes: 6 additions & 0 deletions carbonmark-api/src/app.constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,3 +114,9 @@ export const ICR_API = (

return { ICR_API_URL: API_CONFIG.url, ICR_API_KEY: API_CONFIG.apiKey };
};
/** Message shared with frontend, to be combined with user's nonce and signed by private key. */
export const SIGN_PROFILE_MESSAGE =
process.env.SIGN_PROFILE_MESSAGE || "VerifyCarbonmarkProfileEdit";

/** Ethereum 0x address */
export const VALID_ADDRESS_REGEX = /^0x[a-fA-F0-9]{40}$/;
16 changes: 16 additions & 0 deletions carbonmark-api/src/fastify.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import "fastify";
import { UserProfile } from "./models/UserProfile.model";
import { ILcacheStorage } from "./plugins/caching";

declare module "fastify" {
interface FastifyRequest {
/** Authenticated routes may pass the userDoc down from preHandler */
userProfile?: UserProfile | null;
}
interface FastifyInstance {
lcache: ILcacheStorage;
}
interface FastifyInstance {
firebase: FirebaseInstance;
}
}
1 change: 1 addition & 0 deletions carbonmark-api/src/models/User.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export const UserModel = Type.Object({
listings: Type.Optional(Type.Array(ListingModel)),
activities: Type.Optional(Type.Array(ActivityModel)),
assets: Type.Optional(Type.Array(AssetModel)),
nonce: Type.Optional(Type.Number()),
});

export type User = Static<typeof UserModel>;
Expand Down
32 changes: 25 additions & 7 deletions carbonmark-api/src/models/UserProfile.model.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,33 @@
import { Static, Type } from "@sinclair/typebox";
import { Nullable } from "./Utility.model";

//This model matches the document structure in https://console.firebase.google.com/project/klimadao-staging
/**
* This model matches the document structure in https://console.firebase.google.com/project/klimadao-staging
* Should not be documented in public docs.
* At time of writing we only use this for types, we don't validate firestore writes against this schema.
*
* Updated 09.12.2023 - added nonce
*/

export const UserProfileModel = Type.Object({
handle: Nullable(Type.String()),
username: Type.String(),
description: Nullable(Type.String()),
profileImgUrl: Nullable(Type.String()),
updatedAt: Type.Number(),
/** Unique, immutable, case-insensitive handle */
handle: Type.String({ minLength: 3, maxLength: 24 }),
/** Unix timestamp ms */
createdAt: Type.Number(),
/** Unix timestamp ms */
updatedAt: Type.Number(),
/** Lower case wallet address which owns the profile */
address: Type.String(),
/** Editable username */
username: Type.String({ minLength: 2 }),
/** Optional profile description. May also be empty string. */
description: Type.Optional(Type.String()),
/** Optional image url. May also be empty string. */
profileImgUrl: Type.Optional(Type.String()),
/**
* Nonce, incremented once per edit, may not be present
* Ensures the same message hash can never be reused (replay attack)
* */
nonce: Type.Optional(Type.Number()),
});

export type UserProfile = Static<typeof UserProfileModel>;
35 changes: 0 additions & 35 deletions carbonmark-api/src/plugins/bearer.ts

This file was deleted.

6 changes: 0 additions & 6 deletions carbonmark-api/src/plugins/caching.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,6 @@ export default fp(async function (fastify) {
}
});

declare module "fastify" {
export interface FastifyInstance {
lcache: ILcacheStorage | null;
}
}

export type CachedResponse<T> =
| {
payload: T;
Expand Down
6 changes: 0 additions & 6 deletions carbonmark-api/src/plugins/firebase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,3 @@ export default fp(async function (fastify: FastifyInstance) {
});

export type FirebaseInstance = admin.app.App;

declare module "fastify" {
export interface FastifyInstance {
firebase: FirebaseInstance;
}
}
20 changes: 0 additions & 20 deletions carbonmark-api/src/plugins/session.ts

This file was deleted.

19 changes: 0 additions & 19 deletions carbonmark-api/src/plugins/users.ts

This file was deleted.

Loading

0 comments on commit 62f4b34

Please sign in to comment.