@powerhousedao/reactor-api 1.12.1 → 1.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -4
- package/dist/index.d.ts +64 -19
- package/dist/index.js +1616 -4331
- package/dist/index.js.map +1 -1
- package/package.json +14 -5
- package/src/processors/index.ts +1 -0
- package/src/processors/operational-processor.ts +20 -0
- package/src/server.ts +1 -1
- package/src/subgraphs/auth/env/getters.ts +30 -0
- package/src/subgraphs/auth/env/index.ts +15 -0
- package/src/subgraphs/auth/index.ts +323 -0
- package/src/subgraphs/auth/types.ts +39 -0
- package/src/subgraphs/auth/utils/helpers.ts +136 -0
- package/src/subgraphs/auth/utils/session.ts +149 -0
- package/src/subgraphs/auth/utils/user.ts +40 -0
- package/src/subgraphs/base/index.ts +4 -1
- package/src/subgraphs/index.ts +1 -0
- package/src/subgraphs/manager.ts +5 -12
- package/src/subgraphs/system/env/getters.ts +7 -0
- package/src/subgraphs/system/env/index.ts +6 -0
- package/src/subgraphs/system/index.ts +26 -1
- package/src/subgraphs/system/types.ts +5 -0
- package/src/types.ts +2 -2
- package/src/utils/{get-db-client.ts → db.ts} +4 -3
- package/src/utils/index.ts +1 -1
- package/test/router.test.ts +1 -1
- package/tsconfig.json +1 -1
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
|
2
|
+
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
|
3
|
+
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
|
4
|
+
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
|
5
|
+
/* eslint-disable @typescript-eslint/no-unsafe-argument */
|
|
6
|
+
import { GraphQLError } from "graphql";
|
|
7
|
+
import ms from "ms";
|
|
8
|
+
import { SiweMessage } from "siwe";
|
|
9
|
+
import { JWT_EXPIRATION_PERIOD } from "../env";
|
|
10
|
+
import { Session } from "../types";
|
|
11
|
+
import {
|
|
12
|
+
generateTokenAndSession,
|
|
13
|
+
validateOriginAgainstAllowed,
|
|
14
|
+
verifyToken,
|
|
15
|
+
} from "./helpers";
|
|
16
|
+
import { Db } from "../../../types";
|
|
17
|
+
import { Context } from "src/subgraphs/types";
|
|
18
|
+
|
|
19
|
+
export const createAuthenticationSession = async (
|
|
20
|
+
db: Db,
|
|
21
|
+
userId: string,
|
|
22
|
+
allowedOrigins = ["*"],
|
|
23
|
+
) => {
|
|
24
|
+
return generateTokenAndSession(
|
|
25
|
+
db,
|
|
26
|
+
{
|
|
27
|
+
expiresAt: new Date(
|
|
28
|
+
new Date().getTime() + ms(JWT_EXPIRATION_PERIOD),
|
|
29
|
+
).toISOString(),
|
|
30
|
+
name: "Sign in/Sign up",
|
|
31
|
+
allowedOrigins,
|
|
32
|
+
},
|
|
33
|
+
userId,
|
|
34
|
+
true,
|
|
35
|
+
);
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export const createCustomSession = async (
|
|
39
|
+
db: Db,
|
|
40
|
+
userId: string,
|
|
41
|
+
session: {
|
|
42
|
+
expiryDurationSeconds?: number | null;
|
|
43
|
+
name: string;
|
|
44
|
+
allowedOrigins: string[];
|
|
45
|
+
},
|
|
46
|
+
isUserCreated = false,
|
|
47
|
+
) => {
|
|
48
|
+
return generateTokenAndSession(db, session, userId, isUserCreated);
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
export const listSessions = async (db: Db, userId: string) => {
|
|
52
|
+
return db<Session>("Session").select().where("createdBy", userId);
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
export const revoke = async (db: Db, sessionId: string, userId: string) => {
|
|
56
|
+
const [session] = await db<Session>("Session").select().where({
|
|
57
|
+
id: sessionId,
|
|
58
|
+
userId,
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
if (!session) {
|
|
62
|
+
throw new GraphQLError("Session not found", {
|
|
63
|
+
extensions: { code: "SESSION_NOT_FOUND" },
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
if (session.revokedAt !== null) {
|
|
67
|
+
throw new GraphQLError("Session already revoked", {
|
|
68
|
+
extensions: { code: "SESSION_ALREADY_REVOKED" },
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
await db<Session>("Session")
|
|
72
|
+
.update({
|
|
73
|
+
revokedAt: new Date().toISOString(),
|
|
74
|
+
})
|
|
75
|
+
.where({
|
|
76
|
+
id: sessionId,
|
|
77
|
+
userId,
|
|
78
|
+
});
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
export const authenticate = async (context: Context) => {
|
|
82
|
+
const authorization = context.headers.authorization;
|
|
83
|
+
const db = context.db as Db;
|
|
84
|
+
if (!authorization) {
|
|
85
|
+
throw new GraphQLError("Not authenticated", {
|
|
86
|
+
extensions: { code: "NOT_AUTHENTICATED" },
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
const token = authorization.replace("Bearer ", "");
|
|
90
|
+
const origin = context.headers.origin;
|
|
91
|
+
const session = await getSessionByToken(db, origin, token);
|
|
92
|
+
return session;
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
export const getSessionByToken = async (
|
|
96
|
+
db: Db,
|
|
97
|
+
origin?: string,
|
|
98
|
+
token?: string,
|
|
99
|
+
) => {
|
|
100
|
+
if (!token) {
|
|
101
|
+
throw new GraphQLError("Not authenticated", {
|
|
102
|
+
extensions: { code: "NOT_AUTHENTICATED" },
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
const verificationTokenResult = verifyToken(token);
|
|
106
|
+
if (!verificationTokenResult) {
|
|
107
|
+
throw new GraphQLError("Invalid token", {
|
|
108
|
+
extensions: { code: "INVALID_TOKEN" },
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
const { sessionId } = verificationTokenResult;
|
|
112
|
+
const [session] = await db<Session>("Session").select().where({
|
|
113
|
+
id: sessionId,
|
|
114
|
+
});
|
|
115
|
+
if (!session) {
|
|
116
|
+
throw new GraphQLError("Session not found", {
|
|
117
|
+
extensions: { code: "SESSION_NOT_FOUND" },
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
if (session.revokedAt) {
|
|
121
|
+
throw new GraphQLError("Session expired", {
|
|
122
|
+
extensions: { code: "SESSION_EXPIRED" },
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
if (
|
|
126
|
+
origin &&
|
|
127
|
+
(!session.allowedOrigins ||
|
|
128
|
+
session.allowedOrigins === "*" ||
|
|
129
|
+
session.allowedOrigins.includes(origin))
|
|
130
|
+
) {
|
|
131
|
+
validateOriginAgainstAllowed(session.allowedOrigins, origin);
|
|
132
|
+
}
|
|
133
|
+
return session;
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
export const verifySignature = async (
|
|
137
|
+
parsedMessage: SiweMessage,
|
|
138
|
+
signature: string,
|
|
139
|
+
) => {
|
|
140
|
+
try {
|
|
141
|
+
const response = await parsedMessage.verify({
|
|
142
|
+
time: new Date().toISOString(),
|
|
143
|
+
signature,
|
|
144
|
+
});
|
|
145
|
+
return response;
|
|
146
|
+
} catch (error) {
|
|
147
|
+
throw new GraphQLError("Invalid signature");
|
|
148
|
+
}
|
|
149
|
+
};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { GraphQLError } from "graphql";
|
|
2
|
+
import { Db } from "../../../utils/db";
|
|
3
|
+
|
|
4
|
+
interface User {
|
|
5
|
+
address: string;
|
|
6
|
+
createdAt?: string;
|
|
7
|
+
updatedAt?: string;
|
|
8
|
+
networkId: string;
|
|
9
|
+
chainId: number;
|
|
10
|
+
}
|
|
11
|
+
export const upsertUser = async (db: Db, user: User) => {
|
|
12
|
+
const { AUTH_SIGNUP_DISABLED } = process.env;
|
|
13
|
+
if (AUTH_SIGNUP_DISABLED) {
|
|
14
|
+
throw new GraphQLError("Sign up is disabled");
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const [existingUser] = await db<User>("User")
|
|
18
|
+
.select()
|
|
19
|
+
.where("address", user.address);
|
|
20
|
+
|
|
21
|
+
if (existingUser) {
|
|
22
|
+
return existingUser;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const date = new Date().toISOString();
|
|
26
|
+
const [newUser] = await db<User>("User")
|
|
27
|
+
.insert({
|
|
28
|
+
address: user.address,
|
|
29
|
+
updatedAt: date,
|
|
30
|
+
createdAt: date,
|
|
31
|
+
})
|
|
32
|
+
.returning("*");
|
|
33
|
+
|
|
34
|
+
return newUser;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export const getUser = async (db: Db, address: string) => {
|
|
38
|
+
const [user] = await db<User>("User").select().where("address", address);
|
|
39
|
+
return user;
|
|
40
|
+
};
|
|
@@ -6,10 +6,11 @@ import { GraphQLResolverMap } from "@apollo/subgraph/dist/schema-helper";
|
|
|
6
6
|
import { gql } from "graphql-tag";
|
|
7
7
|
import { Context } from "../types";
|
|
8
8
|
import { Db } from "src/types";
|
|
9
|
+
import { SubgraphManager } from "../manager";
|
|
9
10
|
|
|
10
11
|
export class Subgraph implements ISubgraph {
|
|
11
12
|
name = "example";
|
|
12
|
-
resolvers:
|
|
13
|
+
resolvers: Record<string, any> = {
|
|
13
14
|
Query: {
|
|
14
15
|
hello: () => this.name,
|
|
15
16
|
},
|
|
@@ -20,9 +21,11 @@ export class Subgraph implements ISubgraph {
|
|
|
20
21
|
}
|
|
21
22
|
`;
|
|
22
23
|
reactor: IDocumentDriveServer;
|
|
24
|
+
subgraphManager: SubgraphManager;
|
|
23
25
|
operationalStore: Db;
|
|
24
26
|
constructor(args: SubgraphArgs) {
|
|
25
27
|
this.reactor = args.reactor;
|
|
28
|
+
this.subgraphManager = args.subgraphManager;
|
|
26
29
|
this.operationalStore = args.operationalStore;
|
|
27
30
|
}
|
|
28
31
|
async onSetup() {
|
package/src/subgraphs/index.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { Subgraph } from "./base";
|
|
|
3
3
|
export * as analyticsSubgraph from "./analytics";
|
|
4
4
|
export * as driveSubgraph from "./drive";
|
|
5
5
|
export * as systemSubgraph from "./system";
|
|
6
|
+
export * as authSubgraph from "./auth";
|
|
6
7
|
export * from "./types";
|
|
7
8
|
export { Subgraph } from "./base";
|
|
8
9
|
|
package/src/subgraphs/manager.ts
CHANGED
|
@@ -7,12 +7,13 @@ import cors from "cors";
|
|
|
7
7
|
import { IDocumentDriveServer } from "document-drive";
|
|
8
8
|
import express, { IRouter, Router } from "express";
|
|
9
9
|
import { Db } from "src/types";
|
|
10
|
-
import { Context, SubgraphArgs, SubgraphClass } from ".";
|
|
10
|
+
import { authSubgraph, Context, SubgraphArgs, SubgraphClass } from ".";
|
|
11
11
|
import { createSchema } from "../utils/create-schema";
|
|
12
12
|
import { AnalyticsSubgraph } from "./analytics";
|
|
13
13
|
import { Subgraph } from "./base";
|
|
14
14
|
import { DriveSubgraph } from "./drive";
|
|
15
15
|
import { SystemSubgraph } from "./system";
|
|
16
|
+
import { AuthSubgraph } from "./auth";
|
|
16
17
|
|
|
17
18
|
export class SubgraphManager {
|
|
18
19
|
private reactorRouter: IRouter = Router();
|
|
@@ -26,23 +27,15 @@ export class SubgraphManager {
|
|
|
26
27
|
private readonly operationalStore: Db,
|
|
27
28
|
private readonly analyticsStore: IAnalyticsStore,
|
|
28
29
|
) {
|
|
29
|
-
const args: SubgraphArgs = {
|
|
30
|
-
reactor: this.reactor,
|
|
31
|
-
operationalStore: this.operationalStore,
|
|
32
|
-
analyticsStore: this.analyticsStore,
|
|
33
|
-
subgraphManager: this,
|
|
34
|
-
};
|
|
35
|
-
|
|
36
30
|
// Setup Default subgraphs
|
|
31
|
+
this.registerSubgraph(AuthSubgraph);
|
|
37
32
|
this.registerSubgraph(SystemSubgraph);
|
|
38
33
|
this.registerSubgraph(DriveSubgraph);
|
|
39
34
|
this.registerSubgraph(AnalyticsSubgraph);
|
|
40
35
|
}
|
|
41
36
|
|
|
42
37
|
async init() {
|
|
43
|
-
console.log(
|
|
44
|
-
`Initializing ReactorRouterManager with subgraphs: [${this.subgraphs.map((e) => e.name).join(", ")}]`,
|
|
45
|
-
);
|
|
38
|
+
console.log(`Initializing ReactorRouterManager...`);
|
|
46
39
|
const models = this.reactor.getDocumentModels();
|
|
47
40
|
const driveModel = models.find(
|
|
48
41
|
(it) => it.documentModel.name === "DocumentDrive",
|
|
@@ -115,7 +108,7 @@ export class SubgraphManager {
|
|
|
115
108
|
});
|
|
116
109
|
await subgraphInstance.onSetup();
|
|
117
110
|
this.subgraphs.unshift(subgraphInstance);
|
|
118
|
-
console.log(`> Registered ${subgraphInstance.name} subgraph.`);
|
|
111
|
+
console.log(`> Registered ${this.path.slice(-1) === "/" ? this.path : this.path+ "/"}${subgraphInstance.name} subgraph.`);
|
|
119
112
|
await this.updateRouter();
|
|
120
113
|
}
|
|
121
114
|
|
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
import { DriveInput } from "document-drive";
|
|
2
|
+
import { GraphQLError } from "graphql";
|
|
2
3
|
import { gql } from "graphql-tag";
|
|
3
4
|
import { Subgraph } from "../base";
|
|
5
|
+
import { ADMIN_USERS } from "./env";
|
|
6
|
+
import { SystemContext } from "./types";
|
|
4
7
|
|
|
5
8
|
export class SystemSubgraph extends Subgraph {
|
|
6
9
|
name = "system";
|
|
10
|
+
|
|
7
11
|
typeDefs = gql`
|
|
8
12
|
type Query {
|
|
9
13
|
drives: [String!]!
|
|
@@ -32,8 +36,16 @@ export class SystemSubgraph extends Subgraph {
|
|
|
32
36
|
},
|
|
33
37
|
},
|
|
34
38
|
Mutation: {
|
|
35
|
-
addDrive: async (
|
|
39
|
+
addDrive: async (
|
|
40
|
+
parent: unknown,
|
|
41
|
+
args: DriveInput,
|
|
42
|
+
ctx: SystemContext,
|
|
43
|
+
) => {
|
|
36
44
|
try {
|
|
45
|
+
const isAdmin = ctx.isAdmin(ctx);
|
|
46
|
+
if (!isAdmin) {
|
|
47
|
+
throw new GraphQLError("Unauthorized");
|
|
48
|
+
}
|
|
37
49
|
const drive = await this.reactor.addDrive(args);
|
|
38
50
|
return drive.state.global;
|
|
39
51
|
} catch (e) {
|
|
@@ -43,4 +55,17 @@ export class SystemSubgraph extends Subgraph {
|
|
|
43
55
|
},
|
|
44
56
|
},
|
|
45
57
|
};
|
|
58
|
+
|
|
59
|
+
async onSetup() {
|
|
60
|
+
await super.onSetup();
|
|
61
|
+
this.subgraphManager.setAdditionalContextFields({
|
|
62
|
+
isAdmin: (ctx: SystemContext) => {
|
|
63
|
+
return (
|
|
64
|
+
ADMIN_USERS.length === 0 ||
|
|
65
|
+
(ctx.session.address &&
|
|
66
|
+
ADMIN_USERS.includes(ctx.session.address.toLocaleLowerCase()))
|
|
67
|
+
);
|
|
68
|
+
},
|
|
69
|
+
});
|
|
70
|
+
}
|
|
46
71
|
}
|
package/src/types.ts
CHANGED
|
@@ -3,10 +3,10 @@ import { Document, OperationScope } from "document-model/document";
|
|
|
3
3
|
import { Express } from "express";
|
|
4
4
|
import { ProcessorClass } from "./processors/processor";
|
|
5
5
|
import { SubgraphManager } from "./subgraphs/manager";
|
|
6
|
-
import { Db } from "./utils/
|
|
6
|
+
import { Db } from "./utils/db";
|
|
7
7
|
import { IAnalyticsStore } from "./processors/analytics-processor";
|
|
8
8
|
|
|
9
|
-
export type { Db } from "./utils/
|
|
9
|
+
export type { Db } from "./utils/db";
|
|
10
10
|
|
|
11
11
|
export type IProcessorManager = {
|
|
12
12
|
registerProcessor(module: IProcessor | ProcessorClass): Promise<IProcessor>;
|
|
@@ -16,10 +16,11 @@ export function getDbClient(
|
|
|
16
16
|
): Db {
|
|
17
17
|
const isPg = connectionString && isPG(connectionString);
|
|
18
18
|
const client = isPg ? "pg" : (ClientPgLite as typeof knex.Client);
|
|
19
|
-
|
|
19
|
+
const connection = isPg
|
|
20
|
+
? { connectionString }
|
|
21
|
+
: { pglite: new PGlite(connectionString) };
|
|
20
22
|
return knex({
|
|
21
23
|
client,
|
|
22
|
-
|
|
23
|
-
connection: { pglite: new PGlite(connectionString) },
|
|
24
|
+
connection,
|
|
24
25
|
});
|
|
25
26
|
}
|
package/src/utils/index.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
export * from "./create-schema";
|
|
2
|
-
export * from "./
|
|
2
|
+
export * from "./db";
|
package/test/router.test.ts
CHANGED
|
@@ -4,7 +4,7 @@ import { DocumentModel } from "document-model/document";
|
|
|
4
4
|
import { module as DocumentModelLib } from "document-model/document-model";
|
|
5
5
|
import express from "express";
|
|
6
6
|
import { SubgraphManager } from "src";
|
|
7
|
-
import { getDbClient } from "src/utils/
|
|
7
|
+
import { getDbClient } from "src/utils/db";
|
|
8
8
|
import { describe, expect, it } from "vitest";
|
|
9
9
|
|
|
10
10
|
const documentModels = [
|