@appconda/nextjs 1.0.75 → 1.0.77
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/actions/actionClient.d.ts +6 -0
- package/dist/actions/actionClient.js +14 -15
- package/dist/actions/authOptions.d.ts +6 -0
- package/dist/actions/authOptions.js +215 -0
- package/dist/actions/index.d.ts +1 -0
- package/dist/actions/index.js +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/lib/env.d.ts +1 -0
- package/dist/lib/env.js +2 -0
- package/dist/lib/errors.d.ts +70 -0
- package/dist/lib/errors.js +75 -0
- package/package.json +2 -1
- package/src/actions/actionClient.ts +4 -3
- package/src/actions/authOptions.ts +237 -0
- package/src/actions/index.ts +1 -0
- package/src/index.ts +1 -0
- package/src/lib/env.ts +2 -0
- package/src/lib/errors.ts +138 -0
@@ -2,3 +2,9 @@ export declare const actionClient: import("next-safe-action").SafeActionClient<s
|
|
2
2
|
formErrors: string[];
|
3
3
|
fieldErrors: {};
|
4
4
|
}, readonly []>;
|
5
|
+
export declare const authenticatedActionClient: import("next-safe-action").SafeActionClient<string, undefined, undefined, unknown, {
|
6
|
+
user: any;
|
7
|
+
}, undefined, undefined, undefined, readonly [], {
|
8
|
+
formErrors: string[];
|
9
|
+
fieldErrors: {};
|
10
|
+
}, readonly []>;
|
@@ -1,4 +1,7 @@
|
|
1
|
+
import { getServerSession } from "next-auth";
|
1
2
|
import { DEFAULT_SERVER_ERROR_MESSAGE, createSafeActionClient } from "next-safe-action";
|
3
|
+
import { authOptions } from "./authOptions";
|
4
|
+
import { AuthenticationError, AuthorizationError } from "../lib/errors";
|
2
5
|
export const actionClient = createSafeActionClient({
|
3
6
|
handleServerError(e) {
|
4
7
|
/* if (
|
@@ -17,19 +20,15 @@ export const actionClient = createSafeActionClient({
|
|
17
20
|
return DEFAULT_SERVER_ERROR_MESSAGE;
|
18
21
|
},
|
19
22
|
});
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
}
|
32
|
-
|
33
|
-
return next({ ctx: { user } });
|
23
|
+
export const authenticatedActionClient = actionClient.use(async ({ next }) => {
|
24
|
+
const session = await getServerSession(authOptions);
|
25
|
+
if (!session?.user) {
|
26
|
+
throw new AuthenticationError("Not authenticated");
|
27
|
+
}
|
28
|
+
const userId = session.user.id;
|
29
|
+
const user = await getUser(userId);
|
30
|
+
if (!user) {
|
31
|
+
throw new AuthorizationError("User not found");
|
32
|
+
}
|
33
|
+
return next({ ctx: { user } });
|
34
34
|
});
|
35
|
-
*/
|
@@ -0,0 +1,215 @@
|
|
1
|
+
import CredentialsProvider from "next-auth/providers/credentials";
|
2
|
+
import { cookies } from "next/headers";
|
3
|
+
import { getAppcondaClient } from "../getAppcondaClient";
|
4
|
+
import { Account } from "../modules/account/service";
|
5
|
+
import { env } from "../lib/env";
|
6
|
+
import { getSDKForCurrentUser } from "../getSDKForCurrentUser";
|
7
|
+
import { Query } from "../query";
|
8
|
+
export async function signIn({ userName, password }) {
|
9
|
+
const adminClient = await getAppcondaClient();
|
10
|
+
const account = new Account(adminClient);
|
11
|
+
const session = await account.createEmailPasswordSession(userName, password);
|
12
|
+
const c = await cookies();
|
13
|
+
c.set('a_session', session.secret, {
|
14
|
+
path: "/",
|
15
|
+
httpOnly: true,
|
16
|
+
sameSite: "strict",
|
17
|
+
secure: true,
|
18
|
+
});
|
19
|
+
return session;
|
20
|
+
}
|
21
|
+
export const authOptions = {
|
22
|
+
providers: [
|
23
|
+
CredentialsProvider({
|
24
|
+
id: "credentials",
|
25
|
+
// The name to display on the sign in form (e.g. "Sign in with...")
|
26
|
+
name: "Credentials",
|
27
|
+
// The credentials is used to generate a suitable form on the sign in page.
|
28
|
+
// You can specify whatever fields you are expecting to be submitted.
|
29
|
+
// e.g. domain, username, password, 2FA token, etc.
|
30
|
+
// You can pass any HTML attribute to the <input> tag through the object.
|
31
|
+
credentials: {
|
32
|
+
email: {
|
33
|
+
label: "Email Address",
|
34
|
+
type: "email",
|
35
|
+
placeholder: "Your email address",
|
36
|
+
},
|
37
|
+
password: {
|
38
|
+
label: "Password",
|
39
|
+
type: "password",
|
40
|
+
placeholder: "Your password",
|
41
|
+
},
|
42
|
+
totpCode: { label: "Two-factor Code", type: "input", placeholder: "Code from authenticator app" },
|
43
|
+
backupCode: { label: "Backup Code", type: "input", placeholder: "Two-factor backup code" },
|
44
|
+
},
|
45
|
+
async authorize(credentials, _req) {
|
46
|
+
let user;
|
47
|
+
const appcondaSession = await signIn({ userName: credentials?.email, password: credentials?.password });
|
48
|
+
console.log(credentials);
|
49
|
+
/* try {
|
50
|
+
user = await prisma.user.findUnique({
|
51
|
+
where: {
|
52
|
+
email: credentials?.email,
|
53
|
+
},
|
54
|
+
});
|
55
|
+
} catch (e) {
|
56
|
+
console.error(e);
|
57
|
+
throw Error("Internal server error. Please try again later");
|
58
|
+
}
|
59
|
+
if (!user || !credentials) {
|
60
|
+
throw new Error("Invalid credentials");
|
61
|
+
}
|
62
|
+
if (!user.password) {
|
63
|
+
throw new Error("Invalid credentials");
|
64
|
+
}
|
65
|
+
|
66
|
+
const isValid = await verifyPassword(credentials.password, user.password);
|
67
|
+
|
68
|
+
if (!isValid) {
|
69
|
+
throw new Error("Invalid credentials");
|
70
|
+
}
|
71
|
+
|
72
|
+
if (user.twoFactorEnabled && credentials.backupCode) {
|
73
|
+
if (!ENCRYPTION_KEY) {
|
74
|
+
console.error("Missing encryption key; cannot proceed with backup code login.");
|
75
|
+
throw new Error("Internal Server Error");
|
76
|
+
}
|
77
|
+
|
78
|
+
if (!user.backupCodes) throw new Error("No backup codes found");
|
79
|
+
|
80
|
+
const backupCodes = JSON.parse(symmetricDecrypt(user.backupCodes, ENCRYPTION_KEY));
|
81
|
+
|
82
|
+
// check if user-supplied code matches one
|
83
|
+
const index = backupCodes.indexOf(credentials.backupCode.replaceAll("-", ""));
|
84
|
+
if (index === -1) throw new Error("Invalid backup code");
|
85
|
+
|
86
|
+
// delete verified backup code and re-encrypt remaining
|
87
|
+
backupCodes[index] = null;
|
88
|
+
await prisma.user.update({
|
89
|
+
where: {
|
90
|
+
id: user.id,
|
91
|
+
},
|
92
|
+
data: {
|
93
|
+
backupCodes: symmetricEncrypt(JSON.stringify(backupCodes), ENCRYPTION_KEY),
|
94
|
+
},
|
95
|
+
});
|
96
|
+
} else if (user.twoFactorEnabled) {
|
97
|
+
if (!credentials.totpCode) {
|
98
|
+
throw new Error("second factor required");
|
99
|
+
}
|
100
|
+
|
101
|
+
if (!user.twoFactorSecret) {
|
102
|
+
throw new Error("Internal Server Error");
|
103
|
+
}
|
104
|
+
|
105
|
+
if (!ENCRYPTION_KEY) {
|
106
|
+
throw new Error("Internal Server Error");
|
107
|
+
}
|
108
|
+
|
109
|
+
const secret = symmetricDecrypt(user.twoFactorSecret, ENCRYPTION_KEY);
|
110
|
+
if (secret.length !== 32) {
|
111
|
+
throw new Error("Internal Server Error");
|
112
|
+
}
|
113
|
+
|
114
|
+
const isValidToken = (await import("./totp")).totpAuthenticatorCheck(credentials.totpCode, secret);
|
115
|
+
if (!isValidToken) {
|
116
|
+
throw new Error("Invalid second factor code");
|
117
|
+
}
|
118
|
+
} */
|
119
|
+
console.log("asafdf");
|
120
|
+
return {
|
121
|
+
id: appcondaSession.userId,
|
122
|
+
email: appcondaSession.providerUid,
|
123
|
+
emailVerified: true,
|
124
|
+
imageUrl: "",
|
125
|
+
};
|
126
|
+
},
|
127
|
+
}),
|
128
|
+
CredentialsProvider({
|
129
|
+
id: "token",
|
130
|
+
// The name to display on the sign in form (e.g. "Sign in with...")
|
131
|
+
name: "Token",
|
132
|
+
// The credentials is used to generate a suitable form on the sign in page.
|
133
|
+
// You can specify whatever fields you are expecting to be submitted.
|
134
|
+
// e.g. domain, username, password, 2FA token, etc.
|
135
|
+
// You can pass any HTML attribute to the <input> tag through the object.
|
136
|
+
credentials: {
|
137
|
+
token: {
|
138
|
+
label: "Verification Token",
|
139
|
+
type: "string",
|
140
|
+
},
|
141
|
+
},
|
142
|
+
async authorize(credentials, _req) {
|
143
|
+
let user;
|
144
|
+
/* try {
|
145
|
+
if (!credentials?.token) {
|
146
|
+
throw new Error("Token not found");
|
147
|
+
}
|
148
|
+
const { id } = await verifyToken(credentials?.token);
|
149
|
+
user = await prisma.user.findUnique({
|
150
|
+
where: {
|
151
|
+
id: id,
|
152
|
+
},
|
153
|
+
});
|
154
|
+
} catch (e) {
|
155
|
+
console.error(e);
|
156
|
+
throw new Error("Either a user does not match the provided token or the token is invalid");
|
157
|
+
}
|
158
|
+
|
159
|
+
if (!user) {
|
160
|
+
throw new Error("Either a user does not match the provided token or the token is invalid");
|
161
|
+
}
|
162
|
+
|
163
|
+
if (user.emailVerified) {
|
164
|
+
throw new Error("Email already verified");
|
165
|
+
}
|
166
|
+
|
167
|
+
user = await updateUser(user.id, { emailVerified: new Date() }); */
|
168
|
+
return user || null;
|
169
|
+
},
|
170
|
+
}),
|
171
|
+
// Conditionally add enterprise SSO providers
|
172
|
+
...(env.ENTERPRISE_LICENSE_KEY ? [] : []),
|
173
|
+
],
|
174
|
+
callbacks: {
|
175
|
+
async jwt({ token }) {
|
176
|
+
const { users } = await getSDKForCurrentUser();
|
177
|
+
const userList = await users.list([Query.equal("email", token.email)]);
|
178
|
+
const user = userList.users[0] ?? {};
|
179
|
+
/* const existingUser = await getUserByEmail(token?.email!);
|
180
|
+
|
181
|
+
if (!existingUser) {
|
182
|
+
return token;
|
183
|
+
} */
|
184
|
+
return {
|
185
|
+
...token,
|
186
|
+
profile: { id: user.$id, ...user },
|
187
|
+
};
|
188
|
+
},
|
189
|
+
async session({ session, token }) {
|
190
|
+
// @ts-expect-error
|
191
|
+
session.user.id = token?.id;
|
192
|
+
// @ts-expect-error
|
193
|
+
session.user = token.profile;
|
194
|
+
return session;
|
195
|
+
},
|
196
|
+
async signIn({ user, account }) {
|
197
|
+
/* if (account?.provider === "credentials" || account?.provider === "token") {
|
198
|
+
// check if user's email is verified or not
|
199
|
+
if (!user.emailVerified && !EMAIL_VERIFICATION_DISABLED) {
|
200
|
+
throw new Error("Email Verification is Pending");
|
201
|
+
}
|
202
|
+
return true;
|
203
|
+
}
|
204
|
+
if (ENTERPRISE_LICENSE_KEY) {
|
205
|
+
return handleSSOCallback({ user, account });
|
206
|
+
} */
|
207
|
+
return true;
|
208
|
+
},
|
209
|
+
},
|
210
|
+
pages: {
|
211
|
+
signIn: "/auth/login",
|
212
|
+
signOut: "/auth/logout",
|
213
|
+
error: "/auth/login", // Error code passed in query string as ?error=
|
214
|
+
},
|
215
|
+
};
|
package/dist/actions/index.d.ts
CHANGED
@@ -0,0 +1 @@
|
|
1
|
+
export * from "./actionClient";
|
package/dist/actions/index.js
CHANGED
@@ -0,0 +1 @@
|
|
1
|
+
export * from "./actionClient";
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
package/dist/lib/env.d.ts
CHANGED
package/dist/lib/env.js
CHANGED
@@ -9,6 +9,7 @@ export const env = createEnv({
|
|
9
9
|
APPCONDA_ENDPOINT: z.string(),
|
10
10
|
APPCONDA_CLIENT_ENDPOINT: z.string(),
|
11
11
|
_SERVICE_TOKEN: z.string(),
|
12
|
+
ENTERPRISE_LICENSE_KEY: z.string(),
|
12
13
|
/* AI_AZURE_LLM_API_KEY: z.string().optional(),
|
13
14
|
AI_AZURE_EMBEDDINGS_DEPLOYMENT_ID: z.string().optional(),
|
14
15
|
AI_AZURE_LLM_DEPLOYMENT_ID: z.string().optional(),
|
@@ -113,6 +114,7 @@ export const env = createEnv({
|
|
113
114
|
APPCONDA_ENDPOINT: process.env.APPCONDA_ENDPOINT,
|
114
115
|
APPCONDA_CLIENT_ENDPOINT: process.env.APPCONDA_CLIENT_ENDPOINT,
|
115
116
|
_SERVICE_TOKEN: process.env._SERVICE_TOKEN,
|
117
|
+
ENTERPRISE_LICENSE_KEY: process.env.ENTERPRISE_LICENSE_KEY,
|
116
118
|
},
|
117
119
|
});
|
118
120
|
console.log(env);
|
@@ -0,0 +1,70 @@
|
|
1
|
+
import { z } from "zod";
|
2
|
+
declare class ResourceNotFoundError extends Error {
|
3
|
+
statusCode: number;
|
4
|
+
resourceId: string | null;
|
5
|
+
resourceType: string;
|
6
|
+
constructor(resource: string, id: string | null);
|
7
|
+
}
|
8
|
+
declare class InvalidInputError extends Error {
|
9
|
+
statusCode: number;
|
10
|
+
constructor(message: string);
|
11
|
+
}
|
12
|
+
declare class ValidationError extends Error {
|
13
|
+
statusCode: number;
|
14
|
+
constructor(message: string);
|
15
|
+
}
|
16
|
+
declare class UnknownError extends Error {
|
17
|
+
statusCode: number;
|
18
|
+
constructor(message: string);
|
19
|
+
}
|
20
|
+
declare class DatabaseError extends Error {
|
21
|
+
statusCode: number;
|
22
|
+
constructor(message: string);
|
23
|
+
}
|
24
|
+
declare class UniqueConstraintError extends Error {
|
25
|
+
statusCode: number;
|
26
|
+
constructor(message: string);
|
27
|
+
}
|
28
|
+
declare class ForeignKeyConstraintError extends Error {
|
29
|
+
statusCode: number;
|
30
|
+
constructor(message: string);
|
31
|
+
}
|
32
|
+
declare class OperationNotAllowedError extends Error {
|
33
|
+
statusCode: number;
|
34
|
+
constructor(message: string);
|
35
|
+
}
|
36
|
+
declare class AuthenticationError extends Error {
|
37
|
+
statusCode: number;
|
38
|
+
constructor(message: string);
|
39
|
+
}
|
40
|
+
declare class AuthorizationError extends Error {
|
41
|
+
statusCode: number;
|
42
|
+
constructor(message: string);
|
43
|
+
}
|
44
|
+
interface NetworkError {
|
45
|
+
code: "network_error";
|
46
|
+
message: string;
|
47
|
+
status: number;
|
48
|
+
url: URL;
|
49
|
+
responseMessage?: string;
|
50
|
+
details?: Record<string, string | string[] | number | number[] | boolean | boolean[]>;
|
51
|
+
}
|
52
|
+
interface ForbiddenError {
|
53
|
+
code: "forbidden";
|
54
|
+
message: string;
|
55
|
+
status: number;
|
56
|
+
url: URL;
|
57
|
+
responseMessage?: string;
|
58
|
+
details?: Record<string, string | string[] | number | number[] | boolean | boolean[]>;
|
59
|
+
}
|
60
|
+
export declare const ZErrorHandler: z.ZodFunction<z.ZodTuple<[z.ZodAny], z.ZodUnknown>, z.ZodVoid>;
|
61
|
+
export { ResourceNotFoundError, InvalidInputError, ValidationError, DatabaseError, UniqueConstraintError, UnknownError, ForeignKeyConstraintError, OperationNotAllowedError, AuthenticationError, AuthorizationError, };
|
62
|
+
export type { NetworkError, ForbiddenError };
|
63
|
+
export interface ApiErrorResponse {
|
64
|
+
code: "not_found" | "gone" | "bad_request" | "internal_server_error" | "unauthorized" | "method_not_allowed" | "not_authenticated" | "forbidden" | "network_error";
|
65
|
+
message: string;
|
66
|
+
status: number;
|
67
|
+
url: URL;
|
68
|
+
details?: Record<string, string | string[] | number | number[] | boolean | boolean[]>;
|
69
|
+
responseMessage?: string;
|
70
|
+
}
|
@@ -0,0 +1,75 @@
|
|
1
|
+
import { z } from "zod";
|
2
|
+
class ResourceNotFoundError extends Error {
|
3
|
+
constructor(resource, id) {
|
4
|
+
super(id ? `${resource} with ID ${id} not found` : `${resource} not found`);
|
5
|
+
this.statusCode = 404;
|
6
|
+
this.name = "ResourceNotFoundError";
|
7
|
+
this.resourceType = resource;
|
8
|
+
this.resourceId = id;
|
9
|
+
}
|
10
|
+
}
|
11
|
+
class InvalidInputError extends Error {
|
12
|
+
constructor(message) {
|
13
|
+
super(message);
|
14
|
+
this.statusCode = 400;
|
15
|
+
this.name = "InvalidInputError";
|
16
|
+
}
|
17
|
+
}
|
18
|
+
class ValidationError extends Error {
|
19
|
+
constructor(message) {
|
20
|
+
super(message);
|
21
|
+
this.statusCode = 400;
|
22
|
+
this.name = "ValidationError";
|
23
|
+
}
|
24
|
+
}
|
25
|
+
class UnknownError extends Error {
|
26
|
+
constructor(message) {
|
27
|
+
super(message);
|
28
|
+
this.statusCode = 500;
|
29
|
+
this.name = "UnknownError";
|
30
|
+
}
|
31
|
+
}
|
32
|
+
class DatabaseError extends Error {
|
33
|
+
constructor(message) {
|
34
|
+
super(message);
|
35
|
+
this.statusCode = 500;
|
36
|
+
this.name = "DatabaseError";
|
37
|
+
}
|
38
|
+
}
|
39
|
+
class UniqueConstraintError extends Error {
|
40
|
+
constructor(message) {
|
41
|
+
super(message);
|
42
|
+
this.statusCode = 409;
|
43
|
+
this.name = "UniqueConstraintError";
|
44
|
+
}
|
45
|
+
}
|
46
|
+
class ForeignKeyConstraintError extends Error {
|
47
|
+
constructor(message) {
|
48
|
+
super(message);
|
49
|
+
this.statusCode = 409;
|
50
|
+
this.name = "ForeignKeyConstraintError";
|
51
|
+
}
|
52
|
+
}
|
53
|
+
class OperationNotAllowedError extends Error {
|
54
|
+
constructor(message) {
|
55
|
+
super(message);
|
56
|
+
this.statusCode = 403;
|
57
|
+
this.name = "OperationNotAllowedError";
|
58
|
+
}
|
59
|
+
}
|
60
|
+
class AuthenticationError extends Error {
|
61
|
+
constructor(message) {
|
62
|
+
super(message);
|
63
|
+
this.statusCode = 401;
|
64
|
+
this.name = "AuthenticationError";
|
65
|
+
}
|
66
|
+
}
|
67
|
+
class AuthorizationError extends Error {
|
68
|
+
constructor(message) {
|
69
|
+
super(message);
|
70
|
+
this.statusCode = 403;
|
71
|
+
this.name = "AuthorizationError";
|
72
|
+
}
|
73
|
+
}
|
74
|
+
export const ZErrorHandler = z.function().args(z.any()).returns(z.void());
|
75
|
+
export { ResourceNotFoundError, InvalidInputError, ValidationError, DatabaseError, UniqueConstraintError, UnknownError, ForeignKeyConstraintError, OperationNotAllowedError, AuthenticationError, AuthorizationError, };
|
package/package.json
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
"name": "@appconda/nextjs",
|
3
3
|
"homepage": "https://appconda.io/support",
|
4
4
|
"description": "Appconda is an open-source self-hosted backend server that abstract and simplify complex and repetitive development tasks behind a very simple REST API",
|
5
|
-
"version": "1.0.
|
5
|
+
"version": "1.0.77",
|
6
6
|
"license": "BSD-3-Clause",
|
7
7
|
"main": "dist/index.js",
|
8
8
|
"types": "dist/index.d.ts",
|
@@ -25,6 +25,7 @@
|
|
25
25
|
"unpkg": "dist/iife/sdk.js",
|
26
26
|
"dependencies": {
|
27
27
|
"next": "^15.2.3",
|
28
|
+
"next-auth": "^4.24.11",
|
28
29
|
"next-safe-action": "^7.10.4",
|
29
30
|
"node-fetch-native-with-agent": "^1.7.2",
|
30
31
|
"server-only": "^0.0.1",
|
@@ -1,6 +1,8 @@
|
|
1
1
|
|
2
|
-
|
2
|
+
import { getServerSession } from "next-auth";
|
3
3
|
import { DEFAULT_SERVER_ERROR_MESSAGE, createSafeActionClient } from "next-safe-action";
|
4
|
+
import { authOptions } from "./authOptions";
|
5
|
+
import { AuthenticationError, AuthorizationError } from "../lib/errors";
|
4
6
|
|
5
7
|
|
6
8
|
|
@@ -24,7 +26,7 @@ export const actionClient = createSafeActionClient({
|
|
24
26
|
},
|
25
27
|
});
|
26
28
|
|
27
|
-
|
29
|
+
export const authenticatedActionClient = actionClient.use(async ({ next }) => {
|
28
30
|
const session = await getServerSession(authOptions);
|
29
31
|
if (!session?.user) {
|
30
32
|
throw new AuthenticationError("Not authenticated");
|
@@ -39,4 +41,3 @@ export const actionClient = createSafeActionClient({
|
|
39
41
|
|
40
42
|
return next({ ctx: { user } });
|
41
43
|
});
|
42
|
-
*/
|
@@ -0,0 +1,237 @@
|
|
1
|
+
import type { NextAuthOptions } from "next-auth";
|
2
|
+
import CredentialsProvider from "next-auth/providers/credentials";
|
3
|
+
import { cookies } from "next/headers";
|
4
|
+
import { getAppcondaClient } from "../getAppcondaClient";
|
5
|
+
import { Account } from "../modules/account/service";
|
6
|
+
import { env } from "../lib/env";
|
7
|
+
import { getSDKForCurrentUser } from "../getSDKForCurrentUser";
|
8
|
+
import { Query } from "../query";
|
9
|
+
|
10
|
+
|
11
|
+
export async function signIn({ userName, password }: { userName: string, password: string }) {
|
12
|
+
|
13
|
+
|
14
|
+
const adminClient = await getAppcondaClient();
|
15
|
+
|
16
|
+
const account = new Account(adminClient);
|
17
|
+
|
18
|
+
const session = await account.createEmailPasswordSession(userName, password);
|
19
|
+
|
20
|
+
const c = await cookies();
|
21
|
+
|
22
|
+
c.set('a_session', session.secret, {
|
23
|
+
path: "/",
|
24
|
+
httpOnly: true,
|
25
|
+
sameSite: "strict",
|
26
|
+
secure: true,
|
27
|
+
});
|
28
|
+
|
29
|
+
return session;
|
30
|
+
|
31
|
+
}
|
32
|
+
|
33
|
+
export const authOptions: NextAuthOptions = {
|
34
|
+
providers: [
|
35
|
+
CredentialsProvider({
|
36
|
+
id: "credentials",
|
37
|
+
// The name to display on the sign in form (e.g. "Sign in with...")
|
38
|
+
name: "Credentials",
|
39
|
+
// The credentials is used to generate a suitable form on the sign in page.
|
40
|
+
// You can specify whatever fields you are expecting to be submitted.
|
41
|
+
// e.g. domain, username, password, 2FA token, etc.
|
42
|
+
// You can pass any HTML attribute to the <input> tag through the object.
|
43
|
+
credentials: {
|
44
|
+
email: {
|
45
|
+
label: "Email Address",
|
46
|
+
type: "email",
|
47
|
+
placeholder: "Your email address",
|
48
|
+
},
|
49
|
+
password: {
|
50
|
+
label: "Password",
|
51
|
+
type: "password",
|
52
|
+
placeholder: "Your password",
|
53
|
+
},
|
54
|
+
totpCode: { label: "Two-factor Code", type: "input", placeholder: "Code from authenticator app" },
|
55
|
+
backupCode: { label: "Backup Code", type: "input", placeholder: "Two-factor backup code" },
|
56
|
+
},
|
57
|
+
async authorize(credentials, _req) {
|
58
|
+
let user;
|
59
|
+
const appcondaSession = await signIn({ userName: credentials?.email as string, password: credentials?.password as string });
|
60
|
+
|
61
|
+
console.log(credentials);
|
62
|
+
/* try {
|
63
|
+
user = await prisma.user.findUnique({
|
64
|
+
where: {
|
65
|
+
email: credentials?.email,
|
66
|
+
},
|
67
|
+
});
|
68
|
+
} catch (e) {
|
69
|
+
console.error(e);
|
70
|
+
throw Error("Internal server error. Please try again later");
|
71
|
+
}
|
72
|
+
if (!user || !credentials) {
|
73
|
+
throw new Error("Invalid credentials");
|
74
|
+
}
|
75
|
+
if (!user.password) {
|
76
|
+
throw new Error("Invalid credentials");
|
77
|
+
}
|
78
|
+
|
79
|
+
const isValid = await verifyPassword(credentials.password, user.password);
|
80
|
+
|
81
|
+
if (!isValid) {
|
82
|
+
throw new Error("Invalid credentials");
|
83
|
+
}
|
84
|
+
|
85
|
+
if (user.twoFactorEnabled && credentials.backupCode) {
|
86
|
+
if (!ENCRYPTION_KEY) {
|
87
|
+
console.error("Missing encryption key; cannot proceed with backup code login.");
|
88
|
+
throw new Error("Internal Server Error");
|
89
|
+
}
|
90
|
+
|
91
|
+
if (!user.backupCodes) throw new Error("No backup codes found");
|
92
|
+
|
93
|
+
const backupCodes = JSON.parse(symmetricDecrypt(user.backupCodes, ENCRYPTION_KEY));
|
94
|
+
|
95
|
+
// check if user-supplied code matches one
|
96
|
+
const index = backupCodes.indexOf(credentials.backupCode.replaceAll("-", ""));
|
97
|
+
if (index === -1) throw new Error("Invalid backup code");
|
98
|
+
|
99
|
+
// delete verified backup code and re-encrypt remaining
|
100
|
+
backupCodes[index] = null;
|
101
|
+
await prisma.user.update({
|
102
|
+
where: {
|
103
|
+
id: user.id,
|
104
|
+
},
|
105
|
+
data: {
|
106
|
+
backupCodes: symmetricEncrypt(JSON.stringify(backupCodes), ENCRYPTION_KEY),
|
107
|
+
},
|
108
|
+
});
|
109
|
+
} else if (user.twoFactorEnabled) {
|
110
|
+
if (!credentials.totpCode) {
|
111
|
+
throw new Error("second factor required");
|
112
|
+
}
|
113
|
+
|
114
|
+
if (!user.twoFactorSecret) {
|
115
|
+
throw new Error("Internal Server Error");
|
116
|
+
}
|
117
|
+
|
118
|
+
if (!ENCRYPTION_KEY) {
|
119
|
+
throw new Error("Internal Server Error");
|
120
|
+
}
|
121
|
+
|
122
|
+
const secret = symmetricDecrypt(user.twoFactorSecret, ENCRYPTION_KEY);
|
123
|
+
if (secret.length !== 32) {
|
124
|
+
throw new Error("Internal Server Error");
|
125
|
+
}
|
126
|
+
|
127
|
+
const isValidToken = (await import("./totp")).totpAuthenticatorCheck(credentials.totpCode, secret);
|
128
|
+
if (!isValidToken) {
|
129
|
+
throw new Error("Invalid second factor code");
|
130
|
+
}
|
131
|
+
} */
|
132
|
+
|
133
|
+
console.log("asafdf")
|
134
|
+
|
135
|
+
return {
|
136
|
+
id: appcondaSession.userId,
|
137
|
+
email: appcondaSession.providerUid,
|
138
|
+
emailVerified: true,
|
139
|
+
imageUrl: "",
|
140
|
+
};
|
141
|
+
},
|
142
|
+
}),
|
143
|
+
CredentialsProvider({
|
144
|
+
id: "token",
|
145
|
+
// The name to display on the sign in form (e.g. "Sign in with...")
|
146
|
+
name: "Token",
|
147
|
+
// The credentials is used to generate a suitable form on the sign in page.
|
148
|
+
// You can specify whatever fields you are expecting to be submitted.
|
149
|
+
// e.g. domain, username, password, 2FA token, etc.
|
150
|
+
// You can pass any HTML attribute to the <input> tag through the object.
|
151
|
+
credentials: {
|
152
|
+
token: {
|
153
|
+
label: "Verification Token",
|
154
|
+
type: "string",
|
155
|
+
},
|
156
|
+
},
|
157
|
+
async authorize(credentials, _req) {
|
158
|
+
|
159
|
+
let user;
|
160
|
+
/* try {
|
161
|
+
if (!credentials?.token) {
|
162
|
+
throw new Error("Token not found");
|
163
|
+
}
|
164
|
+
const { id } = await verifyToken(credentials?.token);
|
165
|
+
user = await prisma.user.findUnique({
|
166
|
+
where: {
|
167
|
+
id: id,
|
168
|
+
},
|
169
|
+
});
|
170
|
+
} catch (e) {
|
171
|
+
console.error(e);
|
172
|
+
throw new Error("Either a user does not match the provided token or the token is invalid");
|
173
|
+
}
|
174
|
+
|
175
|
+
if (!user) {
|
176
|
+
throw new Error("Either a user does not match the provided token or the token is invalid");
|
177
|
+
}
|
178
|
+
|
179
|
+
if (user.emailVerified) {
|
180
|
+
throw new Error("Email already verified");
|
181
|
+
}
|
182
|
+
|
183
|
+
user = await updateUser(user.id, { emailVerified: new Date() }); */
|
184
|
+
|
185
|
+
return user || null;
|
186
|
+
},
|
187
|
+
}),
|
188
|
+
// Conditionally add enterprise SSO providers
|
189
|
+
...(env.ENTERPRISE_LICENSE_KEY ? [] : []),
|
190
|
+
],
|
191
|
+
callbacks: {
|
192
|
+
async jwt({ token }) {
|
193
|
+
|
194
|
+
const { users } = await getSDKForCurrentUser();
|
195
|
+
const userList = await users.list([Query.equal("email", token.email!)])
|
196
|
+
|
197
|
+
const user = userList.users[0] ?? {};
|
198
|
+
|
199
|
+
/* const existingUser = await getUserByEmail(token?.email!);
|
200
|
+
|
201
|
+
if (!existingUser) {
|
202
|
+
return token;
|
203
|
+
} */
|
204
|
+
|
205
|
+
return {
|
206
|
+
...token,
|
207
|
+
profile: { id: user.$id, ...user },
|
208
|
+
};
|
209
|
+
},
|
210
|
+
async session({ session, token }) {
|
211
|
+
// @ts-expect-error
|
212
|
+
session.user.id = token?.id;
|
213
|
+
// @ts-expect-error
|
214
|
+
session.user = token.profile;
|
215
|
+
|
216
|
+
return session;
|
217
|
+
},
|
218
|
+
async signIn({ user, account }: { user: any; account: Account | null }) {
|
219
|
+
/* if (account?.provider === "credentials" || account?.provider === "token") {
|
220
|
+
// check if user's email is verified or not
|
221
|
+
if (!user.emailVerified && !EMAIL_VERIFICATION_DISABLED) {
|
222
|
+
throw new Error("Email Verification is Pending");
|
223
|
+
}
|
224
|
+
return true;
|
225
|
+
}
|
226
|
+
if (ENTERPRISE_LICENSE_KEY) {
|
227
|
+
return handleSSOCallback({ user, account });
|
228
|
+
} */
|
229
|
+
return true;
|
230
|
+
},
|
231
|
+
},
|
232
|
+
pages: {
|
233
|
+
signIn: "/auth/login",
|
234
|
+
signOut: "/auth/logout",
|
235
|
+
error: "/auth/login", // Error code passed in query string as ?error=
|
236
|
+
},
|
237
|
+
};
|
package/src/actions/index.ts
CHANGED
@@ -0,0 +1 @@
|
|
1
|
+
export * from "./actionClient";
|
package/src/index.ts
CHANGED
package/src/lib/env.ts
CHANGED
@@ -10,6 +10,7 @@ export const env = createEnv({
|
|
10
10
|
APPCONDA_ENDPOINT: z.string(),
|
11
11
|
APPCONDA_CLIENT_ENDPOINT: z.string(),
|
12
12
|
_SERVICE_TOKEN: z.string(),
|
13
|
+
ENTERPRISE_LICENSE_KEY: z.string(),
|
13
14
|
/* AI_AZURE_LLM_API_KEY: z.string().optional(),
|
14
15
|
AI_AZURE_EMBEDDINGS_DEPLOYMENT_ID: z.string().optional(),
|
15
16
|
AI_AZURE_LLM_DEPLOYMENT_ID: z.string().optional(),
|
@@ -115,6 +116,7 @@ export const env = createEnv({
|
|
115
116
|
APPCONDA_ENDPOINT: process.env.APPCONDA_ENDPOINT,
|
116
117
|
APPCONDA_CLIENT_ENDPOINT: process.env.APPCONDA_CLIENT_ENDPOINT,
|
117
118
|
_SERVICE_TOKEN: process.env._SERVICE_TOKEN,
|
119
|
+
ENTERPRISE_LICENSE_KEY: process.env.ENTERPRISE_LICENSE_KEY,
|
118
120
|
},
|
119
121
|
});
|
120
122
|
|
@@ -0,0 +1,138 @@
|
|
1
|
+
import { z } from "zod";
|
2
|
+
|
3
|
+
class ResourceNotFoundError extends Error {
|
4
|
+
statusCode = 404;
|
5
|
+
resourceId: string | null;
|
6
|
+
resourceType: string;
|
7
|
+
|
8
|
+
constructor(resource: string, id: string | null) {
|
9
|
+
super(id ? `${resource} with ID ${id} not found` : `${resource} not found`);
|
10
|
+
this.name = "ResourceNotFoundError";
|
11
|
+
this.resourceType = resource;
|
12
|
+
this.resourceId = id;
|
13
|
+
}
|
14
|
+
}
|
15
|
+
|
16
|
+
class InvalidInputError extends Error {
|
17
|
+
statusCode = 400;
|
18
|
+
constructor(message: string) {
|
19
|
+
super(message);
|
20
|
+
this.name = "InvalidInputError";
|
21
|
+
}
|
22
|
+
}
|
23
|
+
|
24
|
+
class ValidationError extends Error {
|
25
|
+
statusCode = 400;
|
26
|
+
constructor(message: string) {
|
27
|
+
super(message);
|
28
|
+
this.name = "ValidationError";
|
29
|
+
}
|
30
|
+
}
|
31
|
+
|
32
|
+
class UnknownError extends Error {
|
33
|
+
statusCode = 500;
|
34
|
+
constructor(message: string) {
|
35
|
+
super(message);
|
36
|
+
this.name = "UnknownError";
|
37
|
+
}
|
38
|
+
}
|
39
|
+
|
40
|
+
class DatabaseError extends Error {
|
41
|
+
statusCode = 500;
|
42
|
+
constructor(message: string) {
|
43
|
+
super(message);
|
44
|
+
this.name = "DatabaseError";
|
45
|
+
}
|
46
|
+
}
|
47
|
+
|
48
|
+
class UniqueConstraintError extends Error {
|
49
|
+
statusCode = 409;
|
50
|
+
constructor(message: string) {
|
51
|
+
super(message);
|
52
|
+
this.name = "UniqueConstraintError";
|
53
|
+
}
|
54
|
+
}
|
55
|
+
|
56
|
+
class ForeignKeyConstraintError extends Error {
|
57
|
+
statusCode = 409;
|
58
|
+
constructor(message: string) {
|
59
|
+
super(message);
|
60
|
+
this.name = "ForeignKeyConstraintError";
|
61
|
+
}
|
62
|
+
}
|
63
|
+
|
64
|
+
class OperationNotAllowedError extends Error {
|
65
|
+
statusCode = 403;
|
66
|
+
constructor(message: string) {
|
67
|
+
super(message);
|
68
|
+
this.name = "OperationNotAllowedError";
|
69
|
+
}
|
70
|
+
}
|
71
|
+
|
72
|
+
class AuthenticationError extends Error {
|
73
|
+
statusCode = 401;
|
74
|
+
constructor(message: string) {
|
75
|
+
super(message);
|
76
|
+
this.name = "AuthenticationError";
|
77
|
+
}
|
78
|
+
}
|
79
|
+
|
80
|
+
class AuthorizationError extends Error {
|
81
|
+
statusCode = 403;
|
82
|
+
constructor(message: string) {
|
83
|
+
super(message);
|
84
|
+
this.name = "AuthorizationError";
|
85
|
+
}
|
86
|
+
}
|
87
|
+
|
88
|
+
interface NetworkError {
|
89
|
+
code: "network_error";
|
90
|
+
message: string;
|
91
|
+
status: number;
|
92
|
+
url: URL;
|
93
|
+
responseMessage?: string;
|
94
|
+
details?: Record<string, string | string[] | number | number[] | boolean | boolean[]>;
|
95
|
+
}
|
96
|
+
|
97
|
+
interface ForbiddenError {
|
98
|
+
code: "forbidden";
|
99
|
+
message: string;
|
100
|
+
status: number;
|
101
|
+
url: URL;
|
102
|
+
responseMessage?: string;
|
103
|
+
details?: Record<string, string | string[] | number | number[] | boolean | boolean[]>;
|
104
|
+
}
|
105
|
+
|
106
|
+
export const ZErrorHandler = z.function().args(z.any()).returns(z.void());
|
107
|
+
|
108
|
+
export {
|
109
|
+
ResourceNotFoundError,
|
110
|
+
InvalidInputError,
|
111
|
+
ValidationError,
|
112
|
+
DatabaseError,
|
113
|
+
UniqueConstraintError,
|
114
|
+
UnknownError,
|
115
|
+
ForeignKeyConstraintError,
|
116
|
+
OperationNotAllowedError,
|
117
|
+
AuthenticationError,
|
118
|
+
AuthorizationError,
|
119
|
+
};
|
120
|
+
export type { NetworkError, ForbiddenError };
|
121
|
+
|
122
|
+
export interface ApiErrorResponse {
|
123
|
+
code:
|
124
|
+
| "not_found"
|
125
|
+
| "gone"
|
126
|
+
| "bad_request"
|
127
|
+
| "internal_server_error"
|
128
|
+
| "unauthorized"
|
129
|
+
| "method_not_allowed"
|
130
|
+
| "not_authenticated"
|
131
|
+
| "forbidden"
|
132
|
+
| "network_error";
|
133
|
+
message: string;
|
134
|
+
status: number;
|
135
|
+
url: URL;
|
136
|
+
details?: Record<string, string | string[] | number | number[] | boolean | boolean[]>;
|
137
|
+
responseMessage?: string;
|
138
|
+
}
|