@percepta/create 3.1.5 → 3.3.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/dist/index.js +53 -46
- package/dist/index.js.map +1 -1
- package/dist/{init-OeK4Yk6_.js → init-CtCp7Tv2.js} +3 -3
- package/dist/init-CtCp7Tv2.js.map +1 -0
- package/dist/{status-DC8mvHZj.js → status-CKe4aKso.js} +2 -2
- package/dist/{status-DC8mvHZj.js.map → status-CKe4aKso.js.map} +1 -1
- package/dist/{sync-C5Pd32VM.js → sync-D1vkoofl.js} +2 -2
- package/dist/{sync-C5Pd32VM.js.map → sync-D1vkoofl.js.map} +1 -1
- package/dist/{upstream-F6m8zRBQ.js → upstream-D-LH_1z4.js} +2 -2
- package/dist/{upstream-F6m8zRBQ.js.map → upstream-D-LH_1z4.js.map} +1 -1
- package/package.json +2 -2
- package/template-versions.json +1 -1
- package/templates/monorepo/.github/workflows/access-control.yml +38 -0
- package/templates/monorepo/README.md +42 -2
- package/templates/monorepo/access/README.md +39 -0
- package/templates/monorepo/access/bootstrap-grants.yaml.example +9 -0
- package/templates/monorepo/access/dev-grants.yaml.example +19 -0
- package/templates/monorepo/access/dev-groups.yaml.example +8 -0
- package/templates/monorepo/access/reconcile.yaml.example +11 -0
- package/templates/monorepo/auth/README.md +27 -0
- package/templates/monorepo/auth/drizzle.config.ts +13 -0
- package/templates/monorepo/auth/package.json +29 -0
- package/templates/monorepo/auth/scripts/setup-database.ts +11 -0
- package/templates/monorepo/auth/src/auth.ts +47 -0
- package/templates/monorepo/auth/src/config/database.ts +15 -0
- package/templates/monorepo/auth/src/drizzle/db.ts +8 -0
- package/templates/monorepo/auth/src/drizzle/migrations/0000_shared_auth.sql +89 -0
- package/templates/monorepo/auth/src/drizzle/migrations/meta/_journal.json +13 -0
- package/templates/monorepo/auth/src/drizzle/schema/auth/accounts.ts +7 -0
- package/templates/monorepo/auth/src/drizzle/schema/auth/sessions.ts +7 -0
- package/templates/monorepo/auth/src/drizzle/schema/auth/verifications.ts +6 -0
- package/templates/monorepo/auth/src/drizzle/schema/groups.ts +16 -0
- package/templates/monorepo/auth/src/drizzle/schema/index.ts +5 -0
- package/templates/monorepo/auth/src/drizzle/schema/users.ts +6 -0
- package/templates/monorepo/auth/src/index.ts +1 -0
- package/templates/monorepo/auth/src/scim/README.md +6 -0
- package/templates/monorepo/auth/tsconfig.json +12 -0
- package/templates/monorepo/package.json.template +18 -6
- package/templates/monorepo/pnpm-workspace.yaml +1 -0
- package/templates/webapp/AGENTS.md +13 -6
- package/templates/webapp/README.md +34 -18
- package/templates/webapp/agent-skills/access-control.md +301 -0
- package/templates/webapp/agent-skills/database.md +1 -1
- package/templates/webapp/docker-compose.yml +16 -0
- package/templates/webapp/env.example.template +9 -0
- package/templates/webapp/next.config.ts +1 -0
- package/templates/webapp/package.json.template +8 -4
- package/templates/webapp/scripts/seed.ts +87 -36
- package/templates/webapp/scripts/setup-database.ts +7 -1
- package/templates/webapp/scripts/start.sh +0 -9
- package/templates/webapp/src/access/access.manifest.ts +15 -0
- package/templates/webapp/src/access/schema.zed +7 -0
- package/templates/webapp/src/app/(app)/admin/_lib/PrincipalRoleTable.tsx +113 -0
- package/templates/webapp/src/app/(app)/admin/_lib/accessAdmin.ts +85 -0
- package/templates/webapp/src/app/(app)/admin/groups/page.tsx +117 -0
- package/templates/webapp/src/app/(app)/admin/users/page.tsx +79 -0
- package/templates/webapp/src/app/(app)/layout.tsx +16 -2
- package/templates/webapp/src/app/(app)/page.tsx +1 -12
- package/templates/webapp/src/app/(auth)/auth/signin/page.tsx +2 -5
- package/templates/webapp/src/app/(auth)/auth/signup/page.tsx +2 -5
- package/templates/webapp/src/config/getEnvConfig.ts +8 -0
- package/templates/webapp/src/drizzle/db.ts +3 -4
- package/templates/webapp/src/drizzle/migrations/0000_eager_grandmaster.sql +1 -57
- package/templates/webapp/src/drizzle/migrations/meta/0000_snapshot.json +1 -347
- package/templates/webapp/src/drizzle/schema/index.ts +3 -4
- package/templates/webapp/src/lib/auth/index.ts +6 -81
- package/templates/webapp/src/server/api/root.ts +4 -1
- package/templates/webapp/src/server/api/routers/access.ts +13 -0
- package/templates/webapp/src/server/trpc.ts +42 -8
- package/templates/webapp/src/services/DatabaseService.ts +4 -5
- package/templates/webapp/src/services/access/AppAccessControl.ts +39 -0
- package/dist/init-OeK4Yk6_.js.map +0 -1
- package/templates/webapp/scripts/create-user.ts +0 -47
- package/templates/webapp/src/drizzle/schema/auth/accounts.ts +0 -33
- package/templates/webapp/src/drizzle/schema/auth/sessions.ts +0 -25
- package/templates/webapp/src/drizzle/schema/auth/users.ts +0 -38
- package/templates/webapp/src/drizzle/schema/auth/verifications.ts +0 -19
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import type { Metadata } from "next";
|
|
2
|
-
import { headers } from "next/headers";
|
|
3
2
|
import { redirect } from "next/navigation";
|
|
4
3
|
import { Suspense } from "react";
|
|
5
|
-
import {
|
|
4
|
+
import { getServerSession } from "../../../../lib/auth";
|
|
6
5
|
import { CredentialsSignUpForm } from "./CredentialsSignUpForm";
|
|
7
6
|
|
|
8
7
|
export const metadata: Metadata = {
|
|
@@ -10,9 +9,7 @@ export const metadata: Metadata = {
|
|
|
10
9
|
};
|
|
11
10
|
|
|
12
11
|
export default async function SignUpPage() {
|
|
13
|
-
const session = await
|
|
14
|
-
headers: await headers(),
|
|
15
|
-
});
|
|
12
|
+
const session = await getServerSession();
|
|
16
13
|
|
|
17
14
|
if (session?.user != null) {
|
|
18
15
|
redirect("/");
|
|
@@ -59,6 +59,14 @@ export const { getEnvConfig, schema: ENV_CONFIG_SCHEMA } = createEnvConfig(
|
|
|
59
59
|
ENCRYPTION_SECRET_KEY: z.string().optional(),
|
|
60
60
|
TRIGGER_ENDPOINT_API_KEY: z.string().optional(),
|
|
61
61
|
|
|
62
|
+
// Access Control (SpiceDB):
|
|
63
|
+
SPICEDB_ENDPOINT: z.string().default("localhost:50051"),
|
|
64
|
+
SPICEDB_PRESHARED_KEY: z.string().default("dev-spicedb-token"),
|
|
65
|
+
SPICEDB_INSECURE: z
|
|
66
|
+
.string()
|
|
67
|
+
.transform((value: string): boolean => value === "true" || value === "1")
|
|
68
|
+
.default(true),
|
|
69
|
+
|
|
62
70
|
// LLM/AI providers:
|
|
63
71
|
ANTHROPIC_API_KEY: z.string().optional(),
|
|
64
72
|
OPENAI_API_KEY: z.string().optional(),
|
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
import { type NodePgDatabase, drizzle } from "drizzle-orm/node-postgres";
|
|
2
2
|
import { Pool } from "pg";
|
|
3
3
|
import { getEnvConfig } from "../config/getEnvConfig";
|
|
4
|
-
import * as schema from "./schema";
|
|
5
4
|
import { getPgSearchPathOption } from "./searchPath";
|
|
6
5
|
import { getPgSslConfig } from "./ssl";
|
|
7
6
|
|
|
8
7
|
export const { client, db } = createDb();
|
|
9
8
|
|
|
10
|
-
function createDb(): { client: Pool; db: NodePgDatabase
|
|
9
|
+
function createDb(): { client: Pool; db: NodePgDatabase } {
|
|
11
10
|
const {
|
|
12
11
|
DATABASE_HOST: host,
|
|
13
12
|
DATABASE_PORT: port,
|
|
@@ -18,7 +17,7 @@ function createDb(): { client: Pool; db: NodePgDatabase<typeof schema> } {
|
|
|
18
17
|
DATABASE_USE_SSL: useSSL,
|
|
19
18
|
} = getEnvConfig();
|
|
20
19
|
|
|
21
|
-
const
|
|
20
|
+
const pool = new Pool({
|
|
22
21
|
host,
|
|
23
22
|
port,
|
|
24
23
|
user,
|
|
@@ -28,5 +27,5 @@ function createDb(): { client: Pool; db: NodePgDatabase<typeof schema> } {
|
|
|
28
27
|
options: getPgSearchPathOption(databaseSchema),
|
|
29
28
|
});
|
|
30
29
|
|
|
31
|
-
return { client:
|
|
30
|
+
return { client: pool, db: drizzle(pool) };
|
|
32
31
|
}
|
|
@@ -1,57 +1 @@
|
|
|
1
|
-
|
|
2
|
-
"id" text PRIMARY KEY NOT NULL,
|
|
3
|
-
"user_id" uuid NOT NULL,
|
|
4
|
-
"account_id" text NOT NULL,
|
|
5
|
-
"provider_id" text NOT NULL,
|
|
6
|
-
"access_token" text,
|
|
7
|
-
"refresh_token" text,
|
|
8
|
-
"expires_at" integer,
|
|
9
|
-
"access_token_expires_at" timestamp,
|
|
10
|
-
"refresh_token_expires_at" timestamp,
|
|
11
|
-
"scope" text,
|
|
12
|
-
"id_token" text,
|
|
13
|
-
"password" text,
|
|
14
|
-
"created_at" timestamp NOT NULL,
|
|
15
|
-
"updated_at" timestamp NOT NULL
|
|
16
|
-
);
|
|
17
|
-
--> statement-breakpoint
|
|
18
|
-
CREATE TABLE "session" (
|
|
19
|
-
"id" text PRIMARY KEY NOT NULL,
|
|
20
|
-
"user_id" uuid NOT NULL,
|
|
21
|
-
"token" text NOT NULL,
|
|
22
|
-
"expires_at" timestamp NOT NULL,
|
|
23
|
-
"ip_address" text,
|
|
24
|
-
"user_agent" text,
|
|
25
|
-
"impersonated_by" text,
|
|
26
|
-
"created_at" timestamp NOT NULL,
|
|
27
|
-
"updated_at" timestamp NOT NULL,
|
|
28
|
-
CONSTRAINT "session_token_unique" UNIQUE("token")
|
|
29
|
-
);
|
|
30
|
-
--> statement-breakpoint
|
|
31
|
-
CREATE TABLE "users" (
|
|
32
|
-
"id" uuid PRIMARY KEY NOT NULL,
|
|
33
|
-
"name" text NOT NULL,
|
|
34
|
-
"email" text NOT NULL,
|
|
35
|
-
"email_verified" boolean DEFAULT false NOT NULL,
|
|
36
|
-
"image" text,
|
|
37
|
-
"role" text DEFAULT 'user' NOT NULL,
|
|
38
|
-
"banned" boolean DEFAULT false,
|
|
39
|
-
"ban_reason" text,
|
|
40
|
-
"ban_expires" timestamp,
|
|
41
|
-
"created_at" timestamp DEFAULT now() NOT NULL,
|
|
42
|
-
"updated_at" timestamp DEFAULT now() NOT NULL,
|
|
43
|
-
CONSTRAINT "users_email_unique" UNIQUE("email")
|
|
44
|
-
);
|
|
45
|
-
--> statement-breakpoint
|
|
46
|
-
CREATE TABLE "verification" (
|
|
47
|
-
"id" text PRIMARY KEY NOT NULL,
|
|
48
|
-
"identifier" text NOT NULL,
|
|
49
|
-
"value" text NOT NULL,
|
|
50
|
-
"expires_at" timestamp NOT NULL,
|
|
51
|
-
"created_at" timestamp,
|
|
52
|
-
"updated_at" timestamp
|
|
53
|
-
);
|
|
54
|
-
--> statement-breakpoint
|
|
55
|
-
ALTER TABLE "account" ADD CONSTRAINT "account_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
|
56
|
-
ALTER TABLE "session" ADD CONSTRAINT "session_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
|
57
|
-
CREATE UNIQUE INDEX "lower_email_index" ON "users" USING btree (lower("email"));
|
|
1
|
+
-- App-local migrations go here. Customer-global auth tables are migrated by ../../auth.
|
|
@@ -3,353 +3,7 @@
|
|
|
3
3
|
"prevId": "00000000-0000-0000-0000-000000000000",
|
|
4
4
|
"version": "7",
|
|
5
5
|
"dialect": "postgresql",
|
|
6
|
-
"tables": {
|
|
7
|
-
"public.account": {
|
|
8
|
-
"name": "account",
|
|
9
|
-
"schema": "",
|
|
10
|
-
"columns": {
|
|
11
|
-
"id": {
|
|
12
|
-
"name": "id",
|
|
13
|
-
"type": "text",
|
|
14
|
-
"primaryKey": true,
|
|
15
|
-
"notNull": true
|
|
16
|
-
},
|
|
17
|
-
"user_id": {
|
|
18
|
-
"name": "user_id",
|
|
19
|
-
"type": "uuid",
|
|
20
|
-
"primaryKey": false,
|
|
21
|
-
"notNull": true
|
|
22
|
-
},
|
|
23
|
-
"account_id": {
|
|
24
|
-
"name": "account_id",
|
|
25
|
-
"type": "text",
|
|
26
|
-
"primaryKey": false,
|
|
27
|
-
"notNull": true
|
|
28
|
-
},
|
|
29
|
-
"provider_id": {
|
|
30
|
-
"name": "provider_id",
|
|
31
|
-
"type": "text",
|
|
32
|
-
"primaryKey": false,
|
|
33
|
-
"notNull": true
|
|
34
|
-
},
|
|
35
|
-
"access_token": {
|
|
36
|
-
"name": "access_token",
|
|
37
|
-
"type": "text",
|
|
38
|
-
"primaryKey": false,
|
|
39
|
-
"notNull": false
|
|
40
|
-
},
|
|
41
|
-
"refresh_token": {
|
|
42
|
-
"name": "refresh_token",
|
|
43
|
-
"type": "text",
|
|
44
|
-
"primaryKey": false,
|
|
45
|
-
"notNull": false
|
|
46
|
-
},
|
|
47
|
-
"expires_at": {
|
|
48
|
-
"name": "expires_at",
|
|
49
|
-
"type": "integer",
|
|
50
|
-
"primaryKey": false,
|
|
51
|
-
"notNull": false
|
|
52
|
-
},
|
|
53
|
-
"access_token_expires_at": {
|
|
54
|
-
"name": "access_token_expires_at",
|
|
55
|
-
"type": "timestamp",
|
|
56
|
-
"primaryKey": false,
|
|
57
|
-
"notNull": false
|
|
58
|
-
},
|
|
59
|
-
"refresh_token_expires_at": {
|
|
60
|
-
"name": "refresh_token_expires_at",
|
|
61
|
-
"type": "timestamp",
|
|
62
|
-
"primaryKey": false,
|
|
63
|
-
"notNull": false
|
|
64
|
-
},
|
|
65
|
-
"scope": {
|
|
66
|
-
"name": "scope",
|
|
67
|
-
"type": "text",
|
|
68
|
-
"primaryKey": false,
|
|
69
|
-
"notNull": false
|
|
70
|
-
},
|
|
71
|
-
"id_token": {
|
|
72
|
-
"name": "id_token",
|
|
73
|
-
"type": "text",
|
|
74
|
-
"primaryKey": false,
|
|
75
|
-
"notNull": false
|
|
76
|
-
},
|
|
77
|
-
"password": {
|
|
78
|
-
"name": "password",
|
|
79
|
-
"type": "text",
|
|
80
|
-
"primaryKey": false,
|
|
81
|
-
"notNull": false
|
|
82
|
-
},
|
|
83
|
-
"created_at": {
|
|
84
|
-
"name": "created_at",
|
|
85
|
-
"type": "timestamp",
|
|
86
|
-
"primaryKey": false,
|
|
87
|
-
"notNull": true
|
|
88
|
-
},
|
|
89
|
-
"updated_at": {
|
|
90
|
-
"name": "updated_at",
|
|
91
|
-
"type": "timestamp",
|
|
92
|
-
"primaryKey": false,
|
|
93
|
-
"notNull": true
|
|
94
|
-
}
|
|
95
|
-
},
|
|
96
|
-
"indexes": {},
|
|
97
|
-
"foreignKeys": {
|
|
98
|
-
"account_user_id_users_id_fk": {
|
|
99
|
-
"name": "account_user_id_users_id_fk",
|
|
100
|
-
"tableFrom": "account",
|
|
101
|
-
"tableTo": "users",
|
|
102
|
-
"columnsFrom": ["user_id"],
|
|
103
|
-
"columnsTo": ["id"],
|
|
104
|
-
"onDelete": "cascade",
|
|
105
|
-
"onUpdate": "no action"
|
|
106
|
-
}
|
|
107
|
-
},
|
|
108
|
-
"compositePrimaryKeys": {},
|
|
109
|
-
"uniqueConstraints": {},
|
|
110
|
-
"policies": {},
|
|
111
|
-
"checkConstraints": {},
|
|
112
|
-
"isRLSEnabled": false
|
|
113
|
-
},
|
|
114
|
-
"public.session": {
|
|
115
|
-
"name": "session",
|
|
116
|
-
"schema": "",
|
|
117
|
-
"columns": {
|
|
118
|
-
"id": {
|
|
119
|
-
"name": "id",
|
|
120
|
-
"type": "text",
|
|
121
|
-
"primaryKey": true,
|
|
122
|
-
"notNull": true
|
|
123
|
-
},
|
|
124
|
-
"user_id": {
|
|
125
|
-
"name": "user_id",
|
|
126
|
-
"type": "uuid",
|
|
127
|
-
"primaryKey": false,
|
|
128
|
-
"notNull": true
|
|
129
|
-
},
|
|
130
|
-
"token": {
|
|
131
|
-
"name": "token",
|
|
132
|
-
"type": "text",
|
|
133
|
-
"primaryKey": false,
|
|
134
|
-
"notNull": true
|
|
135
|
-
},
|
|
136
|
-
"expires_at": {
|
|
137
|
-
"name": "expires_at",
|
|
138
|
-
"type": "timestamp",
|
|
139
|
-
"primaryKey": false,
|
|
140
|
-
"notNull": true
|
|
141
|
-
},
|
|
142
|
-
"ip_address": {
|
|
143
|
-
"name": "ip_address",
|
|
144
|
-
"type": "text",
|
|
145
|
-
"primaryKey": false,
|
|
146
|
-
"notNull": false
|
|
147
|
-
},
|
|
148
|
-
"user_agent": {
|
|
149
|
-
"name": "user_agent",
|
|
150
|
-
"type": "text",
|
|
151
|
-
"primaryKey": false,
|
|
152
|
-
"notNull": false
|
|
153
|
-
},
|
|
154
|
-
"impersonated_by": {
|
|
155
|
-
"name": "impersonated_by",
|
|
156
|
-
"type": "text",
|
|
157
|
-
"primaryKey": false,
|
|
158
|
-
"notNull": false
|
|
159
|
-
},
|
|
160
|
-
"created_at": {
|
|
161
|
-
"name": "created_at",
|
|
162
|
-
"type": "timestamp",
|
|
163
|
-
"primaryKey": false,
|
|
164
|
-
"notNull": true
|
|
165
|
-
},
|
|
166
|
-
"updated_at": {
|
|
167
|
-
"name": "updated_at",
|
|
168
|
-
"type": "timestamp",
|
|
169
|
-
"primaryKey": false,
|
|
170
|
-
"notNull": true
|
|
171
|
-
}
|
|
172
|
-
},
|
|
173
|
-
"indexes": {},
|
|
174
|
-
"foreignKeys": {
|
|
175
|
-
"session_user_id_users_id_fk": {
|
|
176
|
-
"name": "session_user_id_users_id_fk",
|
|
177
|
-
"tableFrom": "session",
|
|
178
|
-
"tableTo": "users",
|
|
179
|
-
"columnsFrom": ["user_id"],
|
|
180
|
-
"columnsTo": ["id"],
|
|
181
|
-
"onDelete": "cascade",
|
|
182
|
-
"onUpdate": "no action"
|
|
183
|
-
}
|
|
184
|
-
},
|
|
185
|
-
"compositePrimaryKeys": {},
|
|
186
|
-
"uniqueConstraints": {
|
|
187
|
-
"session_token_unique": {
|
|
188
|
-
"name": "session_token_unique",
|
|
189
|
-
"nullsNotDistinct": false,
|
|
190
|
-
"columns": ["token"]
|
|
191
|
-
}
|
|
192
|
-
},
|
|
193
|
-
"policies": {},
|
|
194
|
-
"checkConstraints": {},
|
|
195
|
-
"isRLSEnabled": false
|
|
196
|
-
},
|
|
197
|
-
"public.users": {
|
|
198
|
-
"name": "users",
|
|
199
|
-
"schema": "",
|
|
200
|
-
"columns": {
|
|
201
|
-
"id": {
|
|
202
|
-
"name": "id",
|
|
203
|
-
"type": "uuid",
|
|
204
|
-
"primaryKey": true,
|
|
205
|
-
"notNull": true
|
|
206
|
-
},
|
|
207
|
-
"name": {
|
|
208
|
-
"name": "name",
|
|
209
|
-
"type": "text",
|
|
210
|
-
"primaryKey": false,
|
|
211
|
-
"notNull": true
|
|
212
|
-
},
|
|
213
|
-
"email": {
|
|
214
|
-
"name": "email",
|
|
215
|
-
"type": "text",
|
|
216
|
-
"primaryKey": false,
|
|
217
|
-
"notNull": true
|
|
218
|
-
},
|
|
219
|
-
"email_verified": {
|
|
220
|
-
"name": "email_verified",
|
|
221
|
-
"type": "boolean",
|
|
222
|
-
"primaryKey": false,
|
|
223
|
-
"notNull": true,
|
|
224
|
-
"default": false
|
|
225
|
-
},
|
|
226
|
-
"image": {
|
|
227
|
-
"name": "image",
|
|
228
|
-
"type": "text",
|
|
229
|
-
"primaryKey": false,
|
|
230
|
-
"notNull": false
|
|
231
|
-
},
|
|
232
|
-
"role": {
|
|
233
|
-
"name": "role",
|
|
234
|
-
"type": "text",
|
|
235
|
-
"primaryKey": false,
|
|
236
|
-
"notNull": true,
|
|
237
|
-
"default": "'user'"
|
|
238
|
-
},
|
|
239
|
-
"banned": {
|
|
240
|
-
"name": "banned",
|
|
241
|
-
"type": "boolean",
|
|
242
|
-
"primaryKey": false,
|
|
243
|
-
"notNull": false,
|
|
244
|
-
"default": false
|
|
245
|
-
},
|
|
246
|
-
"ban_reason": {
|
|
247
|
-
"name": "ban_reason",
|
|
248
|
-
"type": "text",
|
|
249
|
-
"primaryKey": false,
|
|
250
|
-
"notNull": false
|
|
251
|
-
},
|
|
252
|
-
"ban_expires": {
|
|
253
|
-
"name": "ban_expires",
|
|
254
|
-
"type": "timestamp",
|
|
255
|
-
"primaryKey": false,
|
|
256
|
-
"notNull": false
|
|
257
|
-
},
|
|
258
|
-
"created_at": {
|
|
259
|
-
"name": "created_at",
|
|
260
|
-
"type": "timestamp",
|
|
261
|
-
"primaryKey": false,
|
|
262
|
-
"notNull": true,
|
|
263
|
-
"default": "now()"
|
|
264
|
-
},
|
|
265
|
-
"updated_at": {
|
|
266
|
-
"name": "updated_at",
|
|
267
|
-
"type": "timestamp",
|
|
268
|
-
"primaryKey": false,
|
|
269
|
-
"notNull": true,
|
|
270
|
-
"default": "now()"
|
|
271
|
-
}
|
|
272
|
-
},
|
|
273
|
-
"indexes": {
|
|
274
|
-
"lower_email_index": {
|
|
275
|
-
"name": "lower_email_index",
|
|
276
|
-
"columns": [
|
|
277
|
-
{
|
|
278
|
-
"expression": "lower(\"email\")",
|
|
279
|
-
"asc": true,
|
|
280
|
-
"isExpression": true,
|
|
281
|
-
"nulls": "last"
|
|
282
|
-
}
|
|
283
|
-
],
|
|
284
|
-
"isUnique": true,
|
|
285
|
-
"concurrently": false,
|
|
286
|
-
"method": "btree",
|
|
287
|
-
"with": {}
|
|
288
|
-
}
|
|
289
|
-
},
|
|
290
|
-
"foreignKeys": {},
|
|
291
|
-
"compositePrimaryKeys": {},
|
|
292
|
-
"uniqueConstraints": {
|
|
293
|
-
"users_email_unique": {
|
|
294
|
-
"name": "users_email_unique",
|
|
295
|
-
"nullsNotDistinct": false,
|
|
296
|
-
"columns": ["email"]
|
|
297
|
-
}
|
|
298
|
-
},
|
|
299
|
-
"policies": {},
|
|
300
|
-
"checkConstraints": {},
|
|
301
|
-
"isRLSEnabled": false
|
|
302
|
-
},
|
|
303
|
-
"public.verification": {
|
|
304
|
-
"name": "verification",
|
|
305
|
-
"schema": "",
|
|
306
|
-
"columns": {
|
|
307
|
-
"id": {
|
|
308
|
-
"name": "id",
|
|
309
|
-
"type": "text",
|
|
310
|
-
"primaryKey": true,
|
|
311
|
-
"notNull": true
|
|
312
|
-
},
|
|
313
|
-
"identifier": {
|
|
314
|
-
"name": "identifier",
|
|
315
|
-
"type": "text",
|
|
316
|
-
"primaryKey": false,
|
|
317
|
-
"notNull": true
|
|
318
|
-
},
|
|
319
|
-
"value": {
|
|
320
|
-
"name": "value",
|
|
321
|
-
"type": "text",
|
|
322
|
-
"primaryKey": false,
|
|
323
|
-
"notNull": true
|
|
324
|
-
},
|
|
325
|
-
"expires_at": {
|
|
326
|
-
"name": "expires_at",
|
|
327
|
-
"type": "timestamp",
|
|
328
|
-
"primaryKey": false,
|
|
329
|
-
"notNull": true
|
|
330
|
-
},
|
|
331
|
-
"created_at": {
|
|
332
|
-
"name": "created_at",
|
|
333
|
-
"type": "timestamp",
|
|
334
|
-
"primaryKey": false,
|
|
335
|
-
"notNull": false
|
|
336
|
-
},
|
|
337
|
-
"updated_at": {
|
|
338
|
-
"name": "updated_at",
|
|
339
|
-
"type": "timestamp",
|
|
340
|
-
"primaryKey": false,
|
|
341
|
-
"notNull": false
|
|
342
|
-
}
|
|
343
|
-
},
|
|
344
|
-
"indexes": {},
|
|
345
|
-
"foreignKeys": {},
|
|
346
|
-
"compositePrimaryKeys": {},
|
|
347
|
-
"uniqueConstraints": {},
|
|
348
|
-
"policies": {},
|
|
349
|
-
"checkConstraints": {},
|
|
350
|
-
"isRLSEnabled": false
|
|
351
|
-
}
|
|
352
|
-
},
|
|
6
|
+
"tables": {},
|
|
353
7
|
"enums": {},
|
|
354
8
|
"schemas": {},
|
|
355
9
|
"sequences": {},
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
export {
|
|
4
|
-
export { verifications } from "./auth/verifications";
|
|
1
|
+
// App-local resource tables are exported from this module.
|
|
2
|
+
// Customer-global auth tables are exported by @__REPO_NAME__/auth/schema.
|
|
3
|
+
export {};
|
|
@@ -1,85 +1,10 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { admin } from "better-auth/plugins";
|
|
4
|
-
import { getEnvConfig } from "../../config/getEnvConfig";
|
|
5
|
-
import { db } from "../../drizzle/db";
|
|
6
|
-
import { accounts } from "../../drizzle/schema/auth/accounts";
|
|
7
|
-
import { sessions } from "../../drizzle/schema/auth/sessions";
|
|
8
|
-
import { users } from "../../drizzle/schema/auth/users";
|
|
9
|
-
import { verifications } from "../../drizzle/schema/auth/verifications";
|
|
10
|
-
import { getLogger } from "../../services/logger/AppLogger";
|
|
1
|
+
import { auth } from "@__REPO_NAME__/auth";
|
|
2
|
+
import { headers } from "next/headers";
|
|
11
3
|
|
|
12
|
-
|
|
13
|
-
const isBuildPhase = process.env.NEXT_PHASE === "phase-production-build";
|
|
4
|
+
export { auth, type BetterAuthSession } from "@__REPO_NAME__/auth";
|
|
14
5
|
|
|
15
|
-
function
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
const secret = getEnvConfig().BETTER_AUTH_SECRET;
|
|
21
|
-
if (secret == null) {
|
|
22
|
-
throw new Error(
|
|
23
|
-
"BETTER_AUTH_SECRET is required. Set it in your environment variables.",
|
|
24
|
-
);
|
|
25
|
-
}
|
|
26
|
-
return secret;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
function createAuth() {
|
|
30
|
-
const config = getEnvConfig();
|
|
31
|
-
|
|
32
|
-
return betterAuth({
|
|
33
|
-
baseURL: config.BETTER_AUTH_URL,
|
|
34
|
-
secret: getSecret(),
|
|
35
|
-
database: drizzleAdapter(db, {
|
|
36
|
-
provider: "pg",
|
|
37
|
-
schema: {
|
|
38
|
-
user: users,
|
|
39
|
-
session: sessions,
|
|
40
|
-
account: accounts,
|
|
41
|
-
verification: verifications,
|
|
42
|
-
},
|
|
43
|
-
}),
|
|
44
|
-
emailAndPassword: {
|
|
45
|
-
enabled: true,
|
|
46
|
-
},
|
|
47
|
-
plugins: [admin()],
|
|
48
|
-
advanced: {
|
|
49
|
-
database: {
|
|
50
|
-
generateId: false,
|
|
51
|
-
},
|
|
52
|
-
},
|
|
53
|
-
logger: {
|
|
54
|
-
disabled: false,
|
|
55
|
-
},
|
|
6
|
+
export async function getServerSession() {
|
|
7
|
+
return auth.api.getSession({
|
|
8
|
+
headers: await headers(),
|
|
56
9
|
});
|
|
57
10
|
}
|
|
58
|
-
|
|
59
|
-
type Auth = ReturnType<typeof createAuth>;
|
|
60
|
-
|
|
61
|
-
let _auth: Auth | undefined;
|
|
62
|
-
|
|
63
|
-
function getAuth(): Auth {
|
|
64
|
-
if (_auth == null) {
|
|
65
|
-
_auth = createAuth();
|
|
66
|
-
getLogger().info(undefined, "Better Auth initialized");
|
|
67
|
-
}
|
|
68
|
-
return _auth;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
/**
|
|
72
|
-
* Lazy proxy that defers auth initialization to first access (runtime),
|
|
73
|
-
* avoiding build-time evaluation of environment variables.
|
|
74
|
-
*/
|
|
75
|
-
export const auth = new Proxy({} as Auth, {
|
|
76
|
-
get(_target, prop, receiver) {
|
|
77
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
|
|
78
|
-
return Reflect.get(getAuth(), prop, receiver);
|
|
79
|
-
},
|
|
80
|
-
has(_target, prop) {
|
|
81
|
-
return Reflect.has(getAuth(), prop);
|
|
82
|
-
},
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
export type BetterAuthSession = typeof auth.$Infer.Session;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { accessManifest } from "../../../access/access.manifest";
|
|
2
|
+
import { protectedProcedure, requirePermission, router } from "../../trpc";
|
|
3
|
+
|
|
4
|
+
export const accessRouter = router({
|
|
5
|
+
roleManagementStatus: protectedProcedure
|
|
6
|
+
.use(
|
|
7
|
+
requirePermission({
|
|
8
|
+
permission: accessManifest.system.manageRolesPermission,
|
|
9
|
+
resource: accessManifest.system.ref(),
|
|
10
|
+
}),
|
|
11
|
+
)
|
|
12
|
+
.query(() => ({ canManageRoles: true })),
|
|
13
|
+
});
|
|
@@ -1,8 +1,17 @@
|
|
|
1
|
+
import type { PermissionClient, SubjectRef } from "@percepta/access-control";
|
|
2
|
+
import {
|
|
3
|
+
createRequireApplicationAccess,
|
|
4
|
+
createRequirePermission,
|
|
5
|
+
} from "@percepta/access-control/trpc";
|
|
1
6
|
import { TRPCError, initTRPC } from "@trpc/server";
|
|
2
|
-
import { headers } from "next/headers";
|
|
3
7
|
import superjson from "superjson";
|
|
4
|
-
import {
|
|
8
|
+
import { accessManifest } from "../access/access.manifest";
|
|
9
|
+
import { type BetterAuthSession, getServerSession } from "../lib/auth";
|
|
5
10
|
import { AuthContextService } from "../services/AuthContextService";
|
|
11
|
+
import {
|
|
12
|
+
getAccessControl,
|
|
13
|
+
toUserSubject,
|
|
14
|
+
} from "../services/access/AppAccessControl";
|
|
6
15
|
import { getTracer } from "../services/logger/AppLogger";
|
|
7
16
|
|
|
8
17
|
export interface Context {
|
|
@@ -16,16 +25,23 @@ export interface ProtectedContext extends Context {
|
|
|
16
25
|
session: BetterAuthSession;
|
|
17
26
|
}
|
|
18
27
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
}
|
|
28
|
+
const lazyPermissionClient = {
|
|
29
|
+
can(check) {
|
|
30
|
+
return getAccessControl().permissions.can(check);
|
|
31
|
+
},
|
|
32
|
+
} satisfies Pick<PermissionClient, "can">;
|
|
23
33
|
|
|
34
|
+
function getCurrentSubject(ctx: Context): SubjectRef | null {
|
|
35
|
+
const userId = ctx.session?.user.id;
|
|
36
|
+
return userId == null ? null : toUserSubject(userId);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export async function createContext(): Promise<Context> {
|
|
24
40
|
const services: Context["services"] = {
|
|
25
41
|
authContext: AuthContextService.create(),
|
|
26
42
|
};
|
|
27
43
|
|
|
28
|
-
return { session, services };
|
|
44
|
+
return { session: await getServerSession(), services };
|
|
29
45
|
}
|
|
30
46
|
|
|
31
47
|
export const { router, procedure } = initTRPC.context<Context>().create({
|
|
@@ -42,7 +58,7 @@ export const { router, procedure } = initTRPC.context<Context>().create({
|
|
|
42
58
|
},
|
|
43
59
|
});
|
|
44
60
|
|
|
45
|
-
|
|
61
|
+
const requireAuthenticatedUser = procedure.use(
|
|
46
62
|
({ ctx, next }): ReturnType<typeof next<ProtectedContext>> => {
|
|
47
63
|
const {
|
|
48
64
|
session,
|
|
@@ -59,3 +75,21 @@ export const protectedProcedure = procedure.use(
|
|
|
59
75
|
);
|
|
60
76
|
},
|
|
61
77
|
);
|
|
78
|
+
|
|
79
|
+
const requireApplicationAccess =
|
|
80
|
+
createRequireApplicationAccess<ProtectedContext>({
|
|
81
|
+
accessControl: lazyPermissionClient,
|
|
82
|
+
getSubject: getCurrentSubject,
|
|
83
|
+
manifest: accessManifest,
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
export const requirePermission = createRequirePermission<ProtectedContext>({
|
|
87
|
+
accessControl: lazyPermissionClient,
|
|
88
|
+
getSubject: getCurrentSubject,
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
export const protectedProcedure = requireAuthenticatedUser;
|
|
92
|
+
|
|
93
|
+
export const appProcedure = requireAuthenticatedUser.use(
|
|
94
|
+
requireApplicationAccess,
|
|
95
|
+
);
|