prostgles-server 4.2.133 → 4.2.135
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/dist/Auth/AuthHandler.d.ts +66 -0
- package/dist/Auth/AuthHandler.d.ts.map +1 -0
- package/dist/Auth/AuthHandler.js +425 -0
- package/dist/Auth/AuthHandler.js.map +1 -0
- package/dist/Auth/AuthTypes.d.ts +218 -0
- package/dist/Auth/AuthTypes.d.ts.map +1 -0
- package/dist/Auth/AuthTypes.js +3 -0
- package/dist/Auth/AuthTypes.js.map +1 -0
- package/dist/Auth/authInit.d.ts +3 -0
- package/dist/Auth/authInit.d.ts.map +1 -0
- package/dist/Auth/authInit.js +155 -0
- package/dist/Auth/authInit.js.map +1 -0
- package/dist/Auth/getSafeReturnURL.d.ts +2 -0
- package/dist/Auth/getSafeReturnURL.d.ts.map +1 -0
- package/dist/Auth/getSafeReturnURL.js +35 -0
- package/dist/Auth/getSafeReturnURL.js.map +1 -0
- package/dist/Auth/setAuthSignup.d.ts +5 -0
- package/dist/Auth/setAuthSignup.d.ts.map +1 -0
- package/dist/Auth/setAuthSignup.js +85 -0
- package/dist/Auth/setAuthSignup.js.map +1 -0
- package/dist/DboBuilder/DboBuilderTypes.d.ts +3 -3
- package/dist/DboBuilder/DboBuilderTypes.d.ts.map +1 -1
- package/dist/DboBuilder/DboBuilderTypes.js.map +1 -1
- package/dist/DboBuilder/QueryBuilder/getNewQuery.d.ts +1 -1
- package/dist/DboBuilder/ViewHandler/ViewHandler.d.ts +1 -1
- package/dist/DboBuilder/runSQL.d.ts +1 -1
- package/dist/FileManager/initFileManager.js +1 -1
- package/dist/FileManager/initFileManager.js.map +1 -1
- package/dist/Filtering.d.ts +1 -1
- package/dist/Prostgles.d.ts +1 -1
- package/dist/Prostgles.d.ts.map +1 -1
- package/dist/Prostgles.js +1 -1
- package/dist/Prostgles.js.map +1 -1
- package/dist/ProstglesTypes.d.ts +1 -1
- package/dist/ProstglesTypes.d.ts.map +1 -1
- package/dist/PubSubManager/PubSubManager.d.ts +1 -1
- package/dist/PubSubManager/PubSubManager.d.ts.map +1 -1
- package/dist/PubSubManager/PubSubManager.js +4 -3
- package/dist/PubSubManager/PubSubManager.js.map +1 -1
- package/dist/PublishParser/PublishParser.d.ts +2 -2
- package/dist/PublishParser/PublishParser.d.ts.map +1 -1
- package/dist/PublishParser/PublishParser.js.map +1 -1
- package/dist/PublishParser/getFileTableRules.d.ts +1 -1
- package/dist/PublishParser/getFileTableRules.d.ts.map +1 -1
- package/dist/PublishParser/getSchemaFromPublish.d.ts +1 -1
- package/dist/PublishParser/getSchemaFromPublish.d.ts.map +1 -1
- package/dist/PublishParser/getTableRulesWithoutFileTable.d.ts +1 -1
- package/dist/PublishParser/getTableRulesWithoutFileTable.d.ts.map +1 -1
- package/dist/PublishParser/publishTypesAndUtils.d.ts +1 -1
- package/dist/PublishParser/publishTypesAndUtils.d.ts.map +1 -1
- package/dist/PublishParser/publishTypesAndUtils.js.map +1 -1
- package/dist/RestApi.d.ts +1 -1
- package/dist/RestApi.d.ts.map +1 -1
- package/dist/RestApi.js +1 -1
- package/dist/RestApi.js.map +1 -1
- package/dist/SchemaWatch/SchemaWatch.d.ts.map +1 -1
- package/dist/SchemaWatch/SchemaWatch.js +5 -1
- package/dist/SchemaWatch/SchemaWatch.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/initProstgles.js +1 -1
- package/dist/initProstgles.js.map +1 -1
- package/dist/runClientRequest.d.ts +1 -1
- package/dist/runClientRequest.d.ts.map +1 -1
- package/dist/runClientRequest.js +1 -1
- package/dist/runClientRequest.js.map +1 -1
- package/lib/Auth/AuthHandler.ts +474 -0
- package/lib/Auth/AuthTypes.ts +247 -0
- package/lib/Auth/authInit.ts +166 -0
- package/lib/Auth/getSafeReturnURL.ts +35 -0
- package/lib/Auth/setAuthSignup.ts +100 -0
- package/lib/DBSchemaBuilder.ts +1 -1
- package/lib/DboBuilder/DboBuilderTypes.ts +4 -3
- package/lib/FileManager/initFileManager.ts +1 -1
- package/lib/Prostgles.ts +2 -2
- package/lib/ProstglesTypes.ts +1 -1
- package/lib/PubSubManager/PubSubManager.ts +4 -3
- package/lib/PublishParser/PublishParser.ts +3 -3
- package/lib/PublishParser/getFileTableRules.ts +1 -1
- package/lib/PublishParser/getSchemaFromPublish.ts +1 -1
- package/lib/PublishParser/getTableRulesWithoutFileTable.ts +1 -1
- package/lib/PublishParser/publishTypesAndUtils.ts +1 -1
- package/lib/RestApi.ts +2 -1
- package/lib/SchemaWatch/SchemaWatch.ts +5 -1
- package/lib/index.ts +1 -1
- package/lib/initProstgles.ts +1 -1
- package/lib/runClientRequest.ts +3 -3
- package/package.json +14 -4
- package/tests/client/index.ts +9 -7
- package/tests/client/package-lock.json +2827 -143
- package/tests/client/package.json +1 -1
- package/tests/client/tsconfig.json +0 -6
- package/tests/clientFileTests.spec.ts +2 -3
- package/tests/clientOnlyQueries.spec.ts +2 -2
- package/tests/clientRestApi.spec.ts +2 -2
- package/tests/server/index.ts +1 -1
- package/tests/server/package-lock.json +62 -77
- package/tests/server/package.json +2 -2
- package/tests/server/tsconfig.json +1 -2
- package/tests/serverOnlyQueries.spec.ts +2 -2
- package/tsconfig.json +0 -1
- package/lib/AuthHandler.ts +0 -816
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
import { Express, NextFunction, Request, Response } from "express";
|
|
2
|
+
import { AnyObject, EmailSignupType, FieldFilter, UserLike } from "prostgles-types";
|
|
3
|
+
import { DB } from "../Prostgles";
|
|
4
|
+
import { DBOFullyTyped } from "../DBSchemaBuilder";
|
|
5
|
+
import { PRGLIOSocket } from "../DboBuilder/DboBuilderTypes";
|
|
6
|
+
import type { AuthenticateOptions } from "passport";
|
|
7
|
+
import type { StrategyOptions as GoogleStrategy, Profile as GoogleProfile } from "passport-google-oauth20";
|
|
8
|
+
import type { StrategyOptions as GitHubStrategy, Profile as GitHubProfile } from "passport-github2";
|
|
9
|
+
import type { MicrosoftStrategyOptions } from "passport-microsoft";
|
|
10
|
+
import type { StrategyOptions as FacebookStrategy, Profile as FacebookProfile } from "passport-facebook";
|
|
11
|
+
|
|
12
|
+
type Awaitable<T> = T | Promise<T>;
|
|
13
|
+
|
|
14
|
+
export type ExpressReq = Request;
|
|
15
|
+
export type ExpressRes = Response;
|
|
16
|
+
|
|
17
|
+
export type LoginClientInfo = {
|
|
18
|
+
ip_address: string;
|
|
19
|
+
ip_address_remote: string | undefined;
|
|
20
|
+
x_real_ip: string | undefined;
|
|
21
|
+
user_agent: string | undefined;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export type BasicSession = {
|
|
25
|
+
|
|
26
|
+
/** Must be hard to bruteforce */
|
|
27
|
+
sid: string;
|
|
28
|
+
|
|
29
|
+
/** UNIX millisecond timestamp */
|
|
30
|
+
expires: number;
|
|
31
|
+
|
|
32
|
+
/** On expired */
|
|
33
|
+
onExpiration: "redirect" | "show_error";
|
|
34
|
+
};
|
|
35
|
+
export type AuthClientRequest = { socket: PRGLIOSocket } | { httpReq: ExpressReq };
|
|
36
|
+
|
|
37
|
+
type ThirdPartyProviders = {
|
|
38
|
+
facebook?: Pick<FacebookStrategy, "clientID" | "clientSecret"> & {
|
|
39
|
+
// appId: string;
|
|
40
|
+
// appSecret: string;
|
|
41
|
+
authOpts: AuthenticateOptions;
|
|
42
|
+
};
|
|
43
|
+
google?: Pick<GoogleStrategy, "clientID" | "clientSecret"> & {
|
|
44
|
+
authOpts: AuthenticateOptions;
|
|
45
|
+
};
|
|
46
|
+
github?: Pick<GitHubStrategy, "clientID" | "clientSecret"> & {
|
|
47
|
+
authOpts: AuthenticateOptions;
|
|
48
|
+
};
|
|
49
|
+
microsoft?: Pick<MicrosoftStrategyOptions, "clientID" | "clientSecret"> & {
|
|
50
|
+
authOpts: AuthenticateOptions;
|
|
51
|
+
};
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
type SMTPConfig = {
|
|
55
|
+
type: "aws-ses" | "smtp";
|
|
56
|
+
host: string;
|
|
57
|
+
port: number;
|
|
58
|
+
secure: boolean;
|
|
59
|
+
auth: {
|
|
60
|
+
user: string;
|
|
61
|
+
pass: string;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
type RegistrationProviders = ThirdPartyProviders & {
|
|
66
|
+
email: {
|
|
67
|
+
signupType: "withMagicLink" | "withPassword";
|
|
68
|
+
smtp: SMTPConfig
|
|
69
|
+
} | {
|
|
70
|
+
signupType: "withPassword";
|
|
71
|
+
/**
|
|
72
|
+
* If provided, the user will be required to confirm their email address
|
|
73
|
+
*/
|
|
74
|
+
smtp?: SMTPConfig;
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
type RegistrationData =
|
|
79
|
+
| {
|
|
80
|
+
provider: "email";
|
|
81
|
+
profile: {
|
|
82
|
+
username: string;
|
|
83
|
+
password: string;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
| {
|
|
87
|
+
provider: "google";
|
|
88
|
+
profile: GoogleProfile;
|
|
89
|
+
accessToken: string;
|
|
90
|
+
refreshToken: string;
|
|
91
|
+
}
|
|
92
|
+
| {
|
|
93
|
+
provider: "github";
|
|
94
|
+
profile: GitHubProfile;
|
|
95
|
+
accessToken: string;
|
|
96
|
+
refreshToken: string;
|
|
97
|
+
}
|
|
98
|
+
| {
|
|
99
|
+
provider: "facebook";
|
|
100
|
+
profile: FacebookProfile;
|
|
101
|
+
accessToken: string;
|
|
102
|
+
refreshToken: string;
|
|
103
|
+
}
|
|
104
|
+
| {
|
|
105
|
+
provider: "microsoft";
|
|
106
|
+
profile: any;
|
|
107
|
+
accessToken: string;
|
|
108
|
+
refreshToken: string;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export type AuthRegistrationConfig = RegistrationProviders & {
|
|
112
|
+
/**
|
|
113
|
+
* Required for social login callback
|
|
114
|
+
*/
|
|
115
|
+
websiteUrl: string;
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Do something with the registered user
|
|
119
|
+
*/
|
|
120
|
+
onRegister: (data: RegistrationData) => Promise<any>;
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
export type SessionUser<ServerUser extends UserLike = UserLike, ClientUser extends UserLike = UserLike> = {
|
|
124
|
+
/**
|
|
125
|
+
* This user will be available in all serverside prostgles options
|
|
126
|
+
* id and type values will be available in the prostgles.user session variable in postgres
|
|
127
|
+
* */
|
|
128
|
+
user: ServerUser;
|
|
129
|
+
/**
|
|
130
|
+
* Controls which fields from user are available in postgres session variable
|
|
131
|
+
*/
|
|
132
|
+
sessionFields?: FieldFilter<ServerUser>;
|
|
133
|
+
/**
|
|
134
|
+
* User data sent to the authenticated client
|
|
135
|
+
*/
|
|
136
|
+
clientUser: ClientUser;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export type AuthResult<SU = SessionUser> = SU & { sid: string; } | {
|
|
140
|
+
user?: undefined;
|
|
141
|
+
clientUser?: undefined;
|
|
142
|
+
sid?: string;
|
|
143
|
+
} | undefined;
|
|
144
|
+
|
|
145
|
+
export type AuthRequestParams<S, SUser extends SessionUser> = { db: DB, dbo: DBOFullyTyped<S>; getUser: () => Promise<AuthResult<SUser>> }
|
|
146
|
+
|
|
147
|
+
export type Auth<S = void, SUser extends SessionUser = SessionUser> = {
|
|
148
|
+
/**
|
|
149
|
+
* Name of the cookie or socket hadnshake query param that represents the session id.
|
|
150
|
+
* Defaults to "session_id"
|
|
151
|
+
*/
|
|
152
|
+
sidKeyName?: string;
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Response time rounding in milliseconds to prevent timing attacks on login. Login response time should always be a multiple of this value. Defaults to 500 milliseconds
|
|
156
|
+
*/
|
|
157
|
+
responseThrottle?: number;
|
|
158
|
+
|
|
159
|
+
expressConfig?: {
|
|
160
|
+
/**
|
|
161
|
+
* Express app instance. If provided Prostgles will attempt to set sidKeyName to user cookie
|
|
162
|
+
*/
|
|
163
|
+
app: Express;
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Used in allowing logging in through express. Defaults to /login
|
|
167
|
+
*/
|
|
168
|
+
loginRoute?: string;
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Used in allowing logging out through express. Defaults to /logout
|
|
172
|
+
*/
|
|
173
|
+
logoutGetPath?: string;
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Options used in setting the cookie after a successful login
|
|
177
|
+
*/
|
|
178
|
+
cookieOptions?: AnyObject;
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* False by default. If false and userRoutes are provided then the socket will request window.location.reload if the current url is on a user route.
|
|
182
|
+
*/
|
|
183
|
+
disableSocketAuthGuard?: boolean;
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* If provided, any client requests to NOT these routes (or their subroutes) will be redirected to loginRoute (if logged in) and then redirected back to the initial route after logging in
|
|
187
|
+
* If logged in the user is allowed to access these routes
|
|
188
|
+
*/
|
|
189
|
+
publicRoutes?: string[];
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Will attach a app.use listener and will expose getUser
|
|
193
|
+
* Used for blocking access
|
|
194
|
+
*/
|
|
195
|
+
use?: (args: { req: ExpressReq; res: ExpressRes, next: NextFunction } & AuthRequestParams<S, SUser>) => void | Promise<void>;
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Will be called after a GET request is authorised
|
|
199
|
+
* This means that
|
|
200
|
+
*/
|
|
201
|
+
onGetRequestOK?: (
|
|
202
|
+
req: ExpressReq,
|
|
203
|
+
res: ExpressRes,
|
|
204
|
+
params: AuthRequestParams<S, SUser>
|
|
205
|
+
) => any;
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Name of get url parameter used in redirecting user after successful login.
|
|
209
|
+
* Defaults to "returnURL"
|
|
210
|
+
*/
|
|
211
|
+
returnUrlParamName?: string;
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* If defined, will check the magic link id and log in the user and redirect to the returnUrl if set
|
|
215
|
+
*/
|
|
216
|
+
magicLinks?: {
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Will default to /magic-link
|
|
220
|
+
*/
|
|
221
|
+
route?: string;
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Used in creating a session/logging in using a magic link
|
|
225
|
+
*/
|
|
226
|
+
check: (magicId: string, dbo: DBOFullyTyped<S>, db: DB, client: LoginClientInfo) => Awaitable<BasicSession | undefined>;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
registrations?: AuthRegistrationConfig;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* undefined sid is allowed to enable public users
|
|
234
|
+
*/
|
|
235
|
+
getUser: (sid: string | undefined, dbo: DBOFullyTyped<S>, db: DB, client: AuthClientRequest & LoginClientInfo) => Awaitable<AuthResult<SUser>>;
|
|
236
|
+
|
|
237
|
+
register?: (params: AnyObject, dbo: DBOFullyTyped<S>, db: DB) => Awaitable<BasicSession> | BasicSession;
|
|
238
|
+
login?: (params: AnyObject, dbo: DBOFullyTyped<S>, db: DB, client: LoginClientInfo) => Awaitable<BasicSession> | BasicSession;
|
|
239
|
+
logout?: (sid: string | undefined, dbo: DBOFullyTyped<S>, db: DB) => Awaitable<any>;
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* If provided then session info will be saved on socket.__prglCache and reused from there
|
|
243
|
+
*/
|
|
244
|
+
cacheSession?: {
|
|
245
|
+
getSession: (sid: string | undefined, dbo: DBOFullyTyped<S>, db: DB) => Awaitable<BasicSession>
|
|
246
|
+
}
|
|
247
|
+
}
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import { DBOFullyTyped } from "../DBSchemaBuilder";
|
|
2
|
+
import { AuthHandler, getLoginClientInfo, HTTPCODES } from "./AuthHandler";
|
|
3
|
+
import { AuthClientRequest, ExpressReq, ExpressRes } from "./AuthTypes";
|
|
4
|
+
import { setAuthSignup } from "./setAuthSignup";
|
|
5
|
+
|
|
6
|
+
export async function authInit(this: AuthHandler) {
|
|
7
|
+
if (!this.opts) return;
|
|
8
|
+
|
|
9
|
+
this.opts.sidKeyName = this.opts.sidKeyName || "session_id";
|
|
10
|
+
const { sidKeyName, login, getUser, expressConfig } = this.opts;
|
|
11
|
+
this.sidKeyName = this.opts.sidKeyName;
|
|
12
|
+
|
|
13
|
+
if (typeof sidKeyName !== "string" && !login) {
|
|
14
|
+
throw "Invalid auth: Provide { sidKeyName: string } ";
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Why ??? Collision with socket.io ???
|
|
18
|
+
*/
|
|
19
|
+
if (this.sidKeyName === "sid") throw "sidKeyName cannot be 'sid' please provide another name.";
|
|
20
|
+
|
|
21
|
+
if (!getUser) throw "getUser missing from auth config";
|
|
22
|
+
|
|
23
|
+
if (expressConfig) {
|
|
24
|
+
const { app, publicRoutes = [], onGetRequestOK, magicLinks, use } = expressConfig;
|
|
25
|
+
if (publicRoutes.find(r => typeof r !== "string" || !r)) {
|
|
26
|
+
throw "Invalid or empty string provided within publicRoutes "
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
setAuthSignup(expressConfig);
|
|
30
|
+
|
|
31
|
+
if(use){
|
|
32
|
+
app.use((req, res, next) => {
|
|
33
|
+
use({
|
|
34
|
+
req,
|
|
35
|
+
res,
|
|
36
|
+
next,
|
|
37
|
+
getUser: () => this.getUser({ httpReq: req }) as any,
|
|
38
|
+
dbo: this.dbo as DBOFullyTyped,
|
|
39
|
+
db: this.db,
|
|
40
|
+
})
|
|
41
|
+
})
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (magicLinks && this.routes.magicLinks) {
|
|
45
|
+
const { check } = magicLinks;
|
|
46
|
+
if (!check) throw "Check must be defined for magicLinks";
|
|
47
|
+
|
|
48
|
+
app.get(this.routes.magicLinks?.expressRoute, async (req: ExpressReq, res: ExpressRes) => {
|
|
49
|
+
const { id } = req.params ?? {};
|
|
50
|
+
|
|
51
|
+
if (typeof id !== "string" || !id) {
|
|
52
|
+
res.status(HTTPCODES.BAD_REQUEST).json({ msg: "Invalid magic-link id. Expecting a string" });
|
|
53
|
+
} else {
|
|
54
|
+
try {
|
|
55
|
+
const session = await this.throttledFunc(async () => {
|
|
56
|
+
return check(id, this.dbo as any, this.db, getLoginClientInfo({ httpReq: req }));
|
|
57
|
+
});
|
|
58
|
+
if (!session) {
|
|
59
|
+
res.status(HTTPCODES.AUTH_ERROR).json({ msg: "Invalid magic-link" });
|
|
60
|
+
} else {
|
|
61
|
+
this.setCookieAndGoToReturnURLIFSet(session, { req, res });
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
} catch (e) {
|
|
65
|
+
res.status(HTTPCODES.AUTH_ERROR).json({ msg: e });
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const loginRoute = this.routes?.login;
|
|
72
|
+
if (loginRoute) {
|
|
73
|
+
app.post(loginRoute, async (req: ExpressReq, res: ExpressRes) => {
|
|
74
|
+
try {
|
|
75
|
+
const start = Date.now();
|
|
76
|
+
const { sid, expires } = await this.loginThrottled(req.body || {}, getLoginClientInfo({ httpReq: req })) || {};
|
|
77
|
+
await this.prostgles.opts.onLog?.({
|
|
78
|
+
type: "auth",
|
|
79
|
+
command: "login",
|
|
80
|
+
duration: Date.now() - start,
|
|
81
|
+
sid,
|
|
82
|
+
socketId: undefined,
|
|
83
|
+
})
|
|
84
|
+
if (sid) {
|
|
85
|
+
|
|
86
|
+
this.setCookieAndGoToReturnURLIFSet({ sid, expires }, { req, res });
|
|
87
|
+
|
|
88
|
+
} else {
|
|
89
|
+
throw ("Internal error: no user or session")
|
|
90
|
+
}
|
|
91
|
+
} catch (err) {
|
|
92
|
+
console.log(err)
|
|
93
|
+
res.status(HTTPCODES.AUTH_ERROR).json({ err });
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
if (this.routes.logoutGetPath && this.opts.logout) {
|
|
99
|
+
app.get(this.routes.logoutGetPath, async (req: ExpressReq, res: ExpressRes) => {
|
|
100
|
+
const sid = this.validateSid(req?.cookies?.[sidKeyName]);
|
|
101
|
+
if (sid) {
|
|
102
|
+
try {
|
|
103
|
+
await this.throttledFunc(() => {
|
|
104
|
+
return this.opts!.logout!(req?.cookies?.[sidKeyName], this.dbo as any, this.db);
|
|
105
|
+
})
|
|
106
|
+
} catch (err) {
|
|
107
|
+
console.error(err);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
res.redirect("/")
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (Array.isArray(publicRoutes)) {
|
|
115
|
+
|
|
116
|
+
/* Redirect if not logged in and requesting non public content */
|
|
117
|
+
app.get(this.routes.catchAll, async (req: ExpressReq, res: ExpressRes, next) => {
|
|
118
|
+
const clientReq: AuthClientRequest = { httpReq: req }
|
|
119
|
+
const getUser = this.getUser;
|
|
120
|
+
if(this.prostgles.restApi){
|
|
121
|
+
if(Object.values(this.prostgles.restApi.routes).some(restRoute => this.matchesRoute(restRoute.split("/:")[0], req.path))){
|
|
122
|
+
next();
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
try {
|
|
127
|
+
const returnURL = this.getReturnUrl(req);
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Requesting a User route
|
|
131
|
+
*/
|
|
132
|
+
if (this.isUserRoute(req.path)) {
|
|
133
|
+
|
|
134
|
+
/* Check auth. Redirect to login if unauthorized */
|
|
135
|
+
const u = await getUser(clientReq);
|
|
136
|
+
if (!u) {
|
|
137
|
+
res.redirect(`${loginRoute}?returnURL=${encodeURIComponent(req.originalUrl)}`);
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/* If authorized and going to returnUrl then redirect. Otherwise serve file */
|
|
142
|
+
} else if (returnURL && (await getUser(clientReq))) {
|
|
143
|
+
|
|
144
|
+
res.redirect(returnURL);
|
|
145
|
+
return;
|
|
146
|
+
|
|
147
|
+
/** If Logged in and requesting login then redirect to main page */
|
|
148
|
+
} else if (this.matchesRoute(loginRoute, req.path) && (await getUser(clientReq))) {
|
|
149
|
+
|
|
150
|
+
res.redirect("/");
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
onGetRequestOK?.(req, res, { getUser: () => getUser(clientReq), dbo: this.dbo as DBOFullyTyped, db: this.db })
|
|
155
|
+
|
|
156
|
+
} catch (error) {
|
|
157
|
+
console.error(error);
|
|
158
|
+
const errorMessage = typeof error === "string" ? error : error instanceof Error ? error.message : "";
|
|
159
|
+
res.status(HTTPCODES.AUTH_ERROR).json({ msg: "Something went wrong when processing your request" + (errorMessage? (": " + errorMessage) : "") });
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
export const getSafeReturnURL = (returnURL: string, returnUrlParamName: string, quiet = false) => {
|
|
2
|
+
/** Dissalow redirect to other domains */
|
|
3
|
+
if(returnURL) {
|
|
4
|
+
const allowedOrigin = "https://localhost";
|
|
5
|
+
const { origin, pathname, search, searchParams } = new URL(returnURL, allowedOrigin);
|
|
6
|
+
if(
|
|
7
|
+
origin !== allowedOrigin ||
|
|
8
|
+
returnURL !== `${pathname}${search}` ||
|
|
9
|
+
searchParams.get(returnUrlParamName)
|
|
10
|
+
){
|
|
11
|
+
if(!quiet){
|
|
12
|
+
console.error(`Unsafe returnUrl: ${returnURL}. Redirecting to /`);
|
|
13
|
+
}
|
|
14
|
+
return "/";
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
return returnURL;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const issue = ([
|
|
22
|
+
["https://localhost", "/"],
|
|
23
|
+
["//localhost.bad.com", "/"],
|
|
24
|
+
["//localhost.com", "/"],
|
|
25
|
+
["/localhost/com", "/localhost/com"],
|
|
26
|
+
["/localhost/com?here=there", "/localhost/com?here=there"],
|
|
27
|
+
["/localhost/com?returnUrl=there", "/"],
|
|
28
|
+
["//http://localhost.com", "/"],
|
|
29
|
+
["//abc.com", "/"],
|
|
30
|
+
["///abc.com", "/"],
|
|
31
|
+
] as const).find(([returnURL, expected]) => getSafeReturnURL(returnURL, "returnUrl", true) !== expected);
|
|
32
|
+
|
|
33
|
+
if(issue){
|
|
34
|
+
throw new Error(`getSafeReturnURL failed for ${issue[0]}. Expected: ${issue[1]}`);
|
|
35
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { Auth } from './AuthTypes';
|
|
2
|
+
import passport from "passport";
|
|
3
|
+
import { Strategy as GoogleStrategy } from "passport-google-oauth20";
|
|
4
|
+
import { Strategy as GitHubStrategy } from "passport-github2";
|
|
5
|
+
import { Strategy as MicrosoftStrategy } from "passport-microsoft";
|
|
6
|
+
import { Strategy as FacebookStrategy } from "passport-facebook";
|
|
7
|
+
import { AuthSocketSchema, getKeys, isDefined, isEmpty } from "prostgles-types";
|
|
8
|
+
|
|
9
|
+
export const setAuthSignup = ({ registrations, app }: Required<Auth>["expressConfig"]) => {
|
|
10
|
+
if(!registrations) return;
|
|
11
|
+
const { email, onRegister, websiteUrl, ...providers } = registrations;
|
|
12
|
+
if(email){
|
|
13
|
+
app.post("/signup", async (req, res) => {
|
|
14
|
+
const { username, password } = req.body;
|
|
15
|
+
if(typeof username !== "string" || typeof password !== "string"){
|
|
16
|
+
res.status(400).json({ msg: "Invalid username or password" });
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
await onRegister({ provider: "email", profile: { username, password }});
|
|
20
|
+
})
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if(!isEmpty(providers)){
|
|
24
|
+
app.use(passport.initialize());
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
([
|
|
28
|
+
providers.google && {
|
|
29
|
+
providerName: "google" as const,
|
|
30
|
+
config: providers.google,
|
|
31
|
+
strategy: GoogleStrategy,
|
|
32
|
+
},
|
|
33
|
+
providers.github && {
|
|
34
|
+
providerName: "github" as const,
|
|
35
|
+
config: providers.github,
|
|
36
|
+
strategy: GitHubStrategy,
|
|
37
|
+
},
|
|
38
|
+
providers.facebook && {
|
|
39
|
+
providerName: "facebook" as const,
|
|
40
|
+
config: providers.facebook,
|
|
41
|
+
strategy: FacebookStrategy,
|
|
42
|
+
},
|
|
43
|
+
providers.microsoft && {
|
|
44
|
+
providerName: "microsoft" as const,
|
|
45
|
+
config: providers.microsoft,
|
|
46
|
+
strategy: MicrosoftStrategy,
|
|
47
|
+
}
|
|
48
|
+
])
|
|
49
|
+
.filter(isDefined)
|
|
50
|
+
.forEach(({
|
|
51
|
+
config: { authOpts, ...config },
|
|
52
|
+
strategy,
|
|
53
|
+
providerName,
|
|
54
|
+
}) => {
|
|
55
|
+
|
|
56
|
+
const callbackPath = `/auth/${providerName}/callback`;
|
|
57
|
+
passport.use(
|
|
58
|
+
new (strategy as typeof GoogleStrategy)(
|
|
59
|
+
{
|
|
60
|
+
...config as any,
|
|
61
|
+
callbackURL: `${websiteUrl}${callbackPath}`,
|
|
62
|
+
},
|
|
63
|
+
async (accessToken, refreshToken, profile, done) => {
|
|
64
|
+
// This callback is where you would normally store or retrieve user info from the database
|
|
65
|
+
await onRegister({ provider: providerName as "google", accessToken, refreshToken, profile });
|
|
66
|
+
return done(null, profile);
|
|
67
|
+
}
|
|
68
|
+
)
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
app.get(`/auth/${providerName}`,
|
|
72
|
+
passport.authenticate(providerName, authOpts)
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
app.get(callbackPath,
|
|
76
|
+
passport.authenticate(providerName, { failureRedirect: '/' }),
|
|
77
|
+
(req, res) => {
|
|
78
|
+
// Successful authentication, redirect to main page
|
|
79
|
+
res.redirect('/');
|
|
80
|
+
}
|
|
81
|
+
);
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export const getProviders = (registrations: Required<Auth>["expressConfig"]["registrations"]): AuthSocketSchema["providers"] | undefined => {
|
|
86
|
+
if(!registrations) return undefined;
|
|
87
|
+
const { email, websiteUrl, onRegister, ...providers } = registrations;
|
|
88
|
+
if(isEmpty(providers)) return undefined;
|
|
89
|
+
|
|
90
|
+
const result: AuthSocketSchema["providers"] = {}
|
|
91
|
+
getKeys(providers).forEach(providerName => {
|
|
92
|
+
if(providers[providerName]?.clientID){
|
|
93
|
+
result[providerName] = {
|
|
94
|
+
url: `/auth/${providerName}`,
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
return result;
|
|
100
|
+
}
|
package/lib/DBSchemaBuilder.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { AnyObject, DbJoinMaker, DBSchema, isObject, JSONB, SQLHandler, TableHandler, ViewHandler } from "prostgles-types";
|
|
2
2
|
import prostgles from ".";
|
|
3
|
-
import { Auth } from "./
|
|
3
|
+
import { Auth } from "./Auth/AuthTypes";
|
|
4
4
|
import { DboBuilder, escapeTSNames, postgresToTsType } from "./DboBuilder/DboBuilder";
|
|
5
5
|
import { PublishAllOrNothing, PublishParams, PublishTableRule, PublishViewRule, } from "./PublishParser/PublishParser";
|
|
6
6
|
import { getJSONBSchemaTSTypes } from "./JSONBValidation/validation";
|
|
@@ -12,9 +12,10 @@ import {
|
|
|
12
12
|
EXISTS_KEY,
|
|
13
13
|
RawJoinPath,
|
|
14
14
|
SQLHandler,
|
|
15
|
-
TableInfo as TInfo
|
|
15
|
+
TableInfo as TInfo,
|
|
16
|
+
UserLike
|
|
16
17
|
} from "prostgles-types";
|
|
17
|
-
import { BasicSession, ExpressReq
|
|
18
|
+
import { BasicSession, ExpressReq } from "../Auth/AuthTypes";
|
|
18
19
|
import { BasicCallback } from "../PubSubManager/PubSubManager";
|
|
19
20
|
import {
|
|
20
21
|
PublishAllOrNothing
|
|
@@ -182,7 +183,7 @@ export type PRGLIOSocket = {
|
|
|
182
183
|
__prglCache?: {
|
|
183
184
|
session: BasicSession;
|
|
184
185
|
user: UserLike;
|
|
185
|
-
clientUser:
|
|
186
|
+
clientUser: UserLike;
|
|
186
187
|
}
|
|
187
188
|
|
|
188
189
|
_user?: AnyObject
|
|
@@ -5,7 +5,7 @@ import { canCreateTables } from "../DboBuilder/runSQL";
|
|
|
5
5
|
import { Prostgles } from "../Prostgles";
|
|
6
6
|
import { FileManager, HOUR, LocalConfig } from "./FileManager";
|
|
7
7
|
import { runClientRequest } from "../runClientRequest";
|
|
8
|
-
import { HTTPCODES } from "../AuthHandler";
|
|
8
|
+
import { HTTPCODES } from "../Auth/AuthHandler";
|
|
9
9
|
|
|
10
10
|
export async function initFileManager(this: FileManager, prg: Prostgles){
|
|
11
11
|
this.prostgles = prg;
|
package/lib/Prostgles.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*--------------------------------------------------------------------------------------------*/
|
|
5
5
|
|
|
6
6
|
import * as pgPromise from 'pg-promise';
|
|
7
|
-
import { AuthHandler } from "./AuthHandler";
|
|
7
|
+
import { AuthHandler } from "./Auth/AuthHandler";
|
|
8
8
|
import { FileManager } from "./FileManager/FileManager";
|
|
9
9
|
import { SchemaWatch } from "./SchemaWatch/SchemaWatch";
|
|
10
10
|
import { OnInitReason, initProstgles } from "./initProstgles";
|
|
@@ -495,7 +495,7 @@ export class Prostgles {
|
|
|
495
495
|
return aName.localeCompare(bName);
|
|
496
496
|
});
|
|
497
497
|
|
|
498
|
-
const { auth } =
|
|
498
|
+
const { auth } = await this.authHandler.getClientAuth(clientReq);
|
|
499
499
|
|
|
500
500
|
const clientSchema: ClientSchema = {
|
|
501
501
|
schema,
|
package/lib/ProstglesTypes.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
|
|
2
2
|
import { FileColumnConfig } from "prostgles-types";
|
|
3
|
-
import { Auth, AuthRequestParams, SessionUser } from "./
|
|
3
|
+
import { Auth, AuthRequestParams, SessionUser } from "./Auth/AuthTypes";
|
|
4
4
|
import { EventTriggerTagFilter } from "./Event_Trigger_Tags";
|
|
5
5
|
import { CloudClient, ImageOptions, LocalConfig } from "./FileManager/FileManager";
|
|
6
6
|
import { EventInfo } from "./Logging";
|
|
@@ -296,9 +296,10 @@ export class PubSubManager {
|
|
|
296
296
|
}
|
|
297
297
|
}
|
|
298
298
|
|
|
299
|
-
getClientSubs(
|
|
299
|
+
getClientSubs({ channel_name, localFuncs, socket_id }: Pick<Subscription, "localFuncs" | "socket_id" | "channel_name">): Subscription[] {
|
|
300
300
|
return this.subs.filter(s => {
|
|
301
|
-
return s.channel_name ===
|
|
301
|
+
return s.channel_name === channel_name &&
|
|
302
|
+
(matchesLocalFuncs(localFuncs, s.localFuncs) || socket_id && s.socket_id === socket_id)
|
|
302
303
|
});
|
|
303
304
|
}
|
|
304
305
|
|
|
@@ -334,7 +335,7 @@ export class PubSubManager {
|
|
|
334
335
|
const { name: table_name } = table_info;
|
|
335
336
|
|
|
336
337
|
if (!this.dbo?.[table_name]?.find) {
|
|
337
|
-
throw new Error(`
|
|
338
|
+
throw new Error(`this.dbo.${table_name}.find undefined`);
|
|
338
339
|
}
|
|
339
340
|
|
|
340
341
|
try {
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import { Method,
|
|
2
|
-
import { AuthResult, SessionUser } from "../
|
|
1
|
+
import { Method, isObject } from "prostgles-types";
|
|
2
|
+
import { AuthResult, SessionUser } from "../Auth/AuthTypes";
|
|
3
3
|
import { LocalParams } from "../DboBuilder/DboBuilder";
|
|
4
4
|
import { DB, DBHandlerServer, Prostgles } from "../Prostgles";
|
|
5
|
+
import { VoidFunction } from "../SchemaWatch/SchemaWatch";
|
|
5
6
|
import { getFileTableRules } from "./getFileTableRules";
|
|
6
7
|
import { getSchemaFromPublish } from "./getSchemaFromPublish";
|
|
7
8
|
import { getTableRulesWithoutFileTable } from "./getTableRulesWithoutFileTable";
|
|
8
9
|
import { DboTable, DboTableCommand, ParsedPublishTable, PublishMethods, PublishObject, PublishParams, RULE_TO_METHODS, TableRule } from "./publishTypesAndUtils";
|
|
9
|
-
import { VoidFunction } from "../SchemaWatch/SchemaWatch";
|
|
10
10
|
|
|
11
11
|
export class PublishParser {
|
|
12
12
|
publish: any;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { AnyObject, FullFilter, isDefined } from "prostgles-types";
|
|
2
|
-
import { AuthResult } from "../
|
|
2
|
+
import { AuthResult } from "../Auth/AuthTypes";
|
|
3
3
|
import { LocalParams } from "../DboBuilder/DboBuilder";
|
|
4
4
|
import { parseFieldFilter } from "../DboBuilder/ViewHandler/parseFieldFilter";
|
|
5
5
|
import { PublishParser } from "./PublishParser";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { DBSchemaTable, MethodKey, TableInfo, TableSchemaForClient, getKeys, pickKeys } from "prostgles-types";
|
|
2
|
-
import { AuthResult, ExpressReq } from "../
|
|
2
|
+
import { AuthResult, ExpressReq } from "../Auth/AuthTypes";
|
|
3
3
|
import { getErrorAsObject, PRGLIOSocket } from "../DboBuilder/DboBuilder";
|
|
4
4
|
import { PublishObject, PublishParser } from "./PublishParser"
|
|
5
5
|
import { TABLE_METHODS } from "../Prostgles";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { getKeys, isObject } from "prostgles-types";
|
|
2
|
-
import { AuthResult } from "../
|
|
2
|
+
import { AuthResult } from "../Auth/AuthTypes";
|
|
3
3
|
import { TableHandler } from "../DboBuilder/TableHandler/TableHandler";
|
|
4
4
|
import { ViewHandler } from "../DboBuilder/ViewHandler/ViewHandler";
|
|
5
5
|
import { DEFAULT_SYNC_BATCH_SIZE } from "../PubSubManager/PubSubManager";
|