mirror of
https://github.com/logto-io/logto.git
synced 2024-12-30 20:33:54 -05:00
Merge pull request #3147 from logto-io/gao-guard-api
refactor(cloud): implement request auth
This commit is contained in:
commit
6f4063609c
18 changed files with 201 additions and 43 deletions
|
@ -22,12 +22,13 @@
|
||||||
"@logto/schemas": "workspace:*",
|
"@logto/schemas": "workspace:*",
|
||||||
"@logto/shared": "workspace:*",
|
"@logto/shared": "workspace:*",
|
||||||
"@silverhand/essentials": "2.2.0",
|
"@silverhand/essentials": "2.2.0",
|
||||||
"@withtyped/postgres": "^0.6.0",
|
"@withtyped/postgres": "^0.7.0",
|
||||||
"@withtyped/server": "^0.6.0",
|
"@withtyped/server": "^0.7.0",
|
||||||
"chalk": "^5.0.0",
|
"chalk": "^5.0.0",
|
||||||
"dotenv": "^16.0.0",
|
"dotenv": "^16.0.0",
|
||||||
"find-up": "^6.3.0",
|
"find-up": "^6.3.0",
|
||||||
"http-proxy": "^1.18.1",
|
"http-proxy": "^1.18.1",
|
||||||
|
"jose": "^4.11.0",
|
||||||
"mime-types": "^2.1.35",
|
"mime-types": "^2.1.35",
|
||||||
"zod": "^3.20.2"
|
"zod": "^3.20.2"
|
||||||
},
|
},
|
||||||
|
|
15
packages/cloud/src/env-set/index.ts
Normal file
15
packages/cloud/src/env-set/index.ts
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
const getEnv = (key: string) => process.env[key];
|
||||||
|
|
||||||
|
class GlobalValues {
|
||||||
|
public readonly logtoEndpoint = new URL(getEnv('LOGTO_ENDPOINT') ?? 'http://localhost:3002');
|
||||||
|
public readonly dbUrl = getEnv('DB_URL');
|
||||||
|
public readonly isProduction = getEnv('NODE_ENV') === 'production';
|
||||||
|
}
|
||||||
|
|
||||||
|
export const EnvSet = {
|
||||||
|
global: new GlobalValues(),
|
||||||
|
|
||||||
|
get isProduction() {
|
||||||
|
return this.global.isProduction;
|
||||||
|
},
|
||||||
|
};
|
|
@ -1,23 +1,34 @@
|
||||||
|
import { cloudApiIndicator } from '@logto/schemas';
|
||||||
|
import type { RequestContext } from '@withtyped/server';
|
||||||
import createServer, { compose, withRequest } from '@withtyped/server';
|
import createServer, { compose, withRequest } from '@withtyped/server';
|
||||||
import dotenv from 'dotenv';
|
import dotenv from 'dotenv';
|
||||||
import { findUp } from 'find-up';
|
import { findUp } from 'find-up';
|
||||||
|
|
||||||
|
import withAuth from './middleware/with-auth.js';
|
||||||
import withHttpProxy from './middleware/with-http-proxy.js';
|
import withHttpProxy from './middleware/with-http-proxy.js';
|
||||||
|
import withPathname from './middleware/with-pathname.js';
|
||||||
import withSpa from './middleware/with-spa.js';
|
import withSpa from './middleware/with-spa.js';
|
||||||
|
|
||||||
dotenv.config({ path: await findUp('.env', {}) });
|
dotenv.config({ path: await findUp('.env', {}) });
|
||||||
|
|
||||||
|
const { EnvSet } = await import('./env-set/index.js');
|
||||||
const { default: router } = await import('./routes/index.js');
|
const { default: router } = await import('./routes/index.js');
|
||||||
|
|
||||||
const isProduction = process.env.NODE_ENV === 'production';
|
|
||||||
const ignorePathnames = ['/api'];
|
const ignorePathnames = ['/api'];
|
||||||
|
|
||||||
const { listen } = createServer({
|
const { listen } = createServer({
|
||||||
port: 3003,
|
port: 3003,
|
||||||
composer: compose(withRequest())
|
composer: compose(withRequest())
|
||||||
.and(router.routes())
|
|
||||||
.and(
|
.and(
|
||||||
isProduction
|
withPathname(
|
||||||
|
'/api',
|
||||||
|
compose<RequestContext>()
|
||||||
|
.and(withAuth({ endpoint: EnvSet.global.logtoEndpoint, audience: cloudApiIndicator }))
|
||||||
|
.and(router.routes())
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.and(
|
||||||
|
EnvSet.isProduction
|
||||||
? withSpa({ pathname: '/', root: '../console/dist', ignorePathnames })
|
? withSpa({ pathname: '/', root: '../console/dist', ignorePathnames })
|
||||||
: withHttpProxy('/', {
|
: withHttpProxy('/', {
|
||||||
target: 'http://localhost:5002',
|
target: 'http://localhost:5002',
|
||||||
|
|
87
packages/cloud/src/middleware/with-auth.ts
Normal file
87
packages/cloud/src/middleware/with-auth.ts
Normal file
|
@ -0,0 +1,87 @@
|
||||||
|
import assert from 'node:assert';
|
||||||
|
import type { IncomingHttpHeaders } from 'node:http';
|
||||||
|
import path from 'node:path/posix';
|
||||||
|
|
||||||
|
import type { NextFunction, RequestContext } from '@withtyped/server';
|
||||||
|
import { RequestError } from '@withtyped/server';
|
||||||
|
import { createRemoteJWKSet, jwtVerify } from 'jose';
|
||||||
|
import { z } from 'zod';
|
||||||
|
|
||||||
|
const bearerTokenIdentifier = 'Bearer';
|
||||||
|
|
||||||
|
export const extractBearerTokenFromHeaders = ({ authorization }: IncomingHttpHeaders) => {
|
||||||
|
assert(authorization, new RequestError('Authorization header is missing.', 401));
|
||||||
|
assert(
|
||||||
|
authorization.startsWith(bearerTokenIdentifier),
|
||||||
|
new RequestError(
|
||||||
|
`Authorization token type is not supported. Valid type: "${bearerTokenIdentifier}".`,
|
||||||
|
401
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
return authorization.slice(bearerTokenIdentifier.length + 1);
|
||||||
|
};
|
||||||
|
|
||||||
|
export type WithAuthContext<Context = RequestContext> = Context & {
|
||||||
|
auth: {
|
||||||
|
id: string;
|
||||||
|
scopes: string[];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export type WithAuthConfig = {
|
||||||
|
/** The Logto admin tenant endpoint. */
|
||||||
|
endpoint: URL;
|
||||||
|
/** The audience (i.e. Resource Indicator) to expect. */
|
||||||
|
audience: string;
|
||||||
|
/** The scopes (i.e. permissions) to expect. */
|
||||||
|
scopes?: string[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function withAuth<InputContext extends RequestContext>({
|
||||||
|
endpoint,
|
||||||
|
audience,
|
||||||
|
scopes: expectScopes = [],
|
||||||
|
}: WithAuthConfig) {
|
||||||
|
const getJwkSet = (async () => {
|
||||||
|
const fetched = await fetch(
|
||||||
|
new URL(path.join(endpoint.pathname, 'oidc/.well-known/openid-configuration'), endpoint)
|
||||||
|
);
|
||||||
|
const { jwks_uri: jwksUri, issuer } = z
|
||||||
|
.object({ jwks_uri: z.string(), issuer: z.string() })
|
||||||
|
.parse(await fetched.json());
|
||||||
|
|
||||||
|
return Object.freeze([createRemoteJWKSet(new URL(jwksUri)), issuer] as const);
|
||||||
|
})();
|
||||||
|
|
||||||
|
return async (context: InputContext, next: NextFunction<WithAuthContext<InputContext>>) => {
|
||||||
|
const [getKey, issuer] = await getJwkSet;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const {
|
||||||
|
payload: { sub, scope },
|
||||||
|
} = await jwtVerify(extractBearerTokenFromHeaders(context.request.headers), getKey, {
|
||||||
|
issuer,
|
||||||
|
audience,
|
||||||
|
});
|
||||||
|
|
||||||
|
assert(sub, new RequestError('"sub" is missing in JWT.', 401));
|
||||||
|
|
||||||
|
const scopes = typeof scope === 'string' ? scope.split(' ') : [];
|
||||||
|
assert(
|
||||||
|
expectScopes.every((scope) => scopes.includes(scope)),
|
||||||
|
new RequestError('Forbidden. Please check your permissions.', 403)
|
||||||
|
);
|
||||||
|
|
||||||
|
await next({ ...context, auth: { id: sub, scopes } });
|
||||||
|
|
||||||
|
return;
|
||||||
|
} catch (error: unknown) {
|
||||||
|
if (error instanceof RequestError) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new RequestError('Unauthorized.', 401);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
|
@ -8,9 +8,7 @@ import { matchPathname } from '#src/utils/url.js';
|
||||||
const { createProxy } = HttpProxy;
|
const { createProxy } = HttpProxy;
|
||||||
|
|
||||||
export type WithHttpProxyOptions = ServerOptions & {
|
export type WithHttpProxyOptions = ServerOptions & {
|
||||||
/**
|
/** An array of pathname prefixes to ignore. */
|
||||||
* An array of pathname prefixes to ignore.
|
|
||||||
*/
|
|
||||||
ignorePathnames?: string[];
|
ignorePathnames?: string[];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -36,9 +34,9 @@ export default function withHttpProxy<InputContext extends RequestContext>(
|
||||||
request: { url },
|
request: { url },
|
||||||
} = context;
|
} = context;
|
||||||
|
|
||||||
const matched = matchPathname(pathname, url.pathname);
|
const matched = matchPathname(pathname, url.pathname, ignorePathnames);
|
||||||
|
|
||||||
if (!matched || ignorePathnames?.some((prefix) => matchPathname(prefix, url.pathname))) {
|
if (!matched) {
|
||||||
return next(context);
|
return next(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
31
packages/cloud/src/middleware/with-pathname.ts
Normal file
31
packages/cloud/src/middleware/with-pathname.ts
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
import type {
|
||||||
|
HttpContext,
|
||||||
|
MiddlewareFunction,
|
||||||
|
NextFunction,
|
||||||
|
RequestContext,
|
||||||
|
} from '@withtyped/server';
|
||||||
|
|
||||||
|
import { matchPathname } from '#src/utils/url.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build a middleware function that conditionally runs the given middleware function based on pathname prefix.
|
||||||
|
*
|
||||||
|
* @param pathname The pathname prefix to match.
|
||||||
|
* @param run The middleware function to run with the prefix matches.
|
||||||
|
*/
|
||||||
|
export default function withPathname<
|
||||||
|
InputContext extends RequestContext,
|
||||||
|
OutputContext extends RequestContext
|
||||||
|
>(pathname: string, run: MiddlewareFunction<InputContext, InputContext | OutputContext>) {
|
||||||
|
return async (
|
||||||
|
context: InputContext,
|
||||||
|
next: NextFunction<InputContext | OutputContext>,
|
||||||
|
httpContext: HttpContext
|
||||||
|
) => {
|
||||||
|
if (!matchPathname(pathname, context.request.url.pathname)) {
|
||||||
|
return next(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
return run(context, next, httpContext);
|
||||||
|
};
|
||||||
|
}
|
|
@ -21,9 +21,7 @@ export type WithSpaConfig = {
|
||||||
* @default '/'
|
* @default '/'
|
||||||
*/
|
*/
|
||||||
pathname?: string;
|
pathname?: string;
|
||||||
/**
|
/** An array of pathname prefixes to ignore. */
|
||||||
* An array of pathname prefixes to ignore.
|
|
||||||
*/
|
|
||||||
ignorePathnames?: string[];
|
ignorePathnames?: string[];
|
||||||
/**
|
/**
|
||||||
* The path to file to serve when the given path cannot be found in the file system.
|
* The path to file to serve when the given path cannot be found in the file system.
|
||||||
|
@ -47,9 +45,9 @@ export default function withSpa<InputContext extends RequestContext>({
|
||||||
request: { url },
|
request: { url },
|
||||||
} = context;
|
} = context;
|
||||||
|
|
||||||
const pathname = matchPathname(rootPathname, url.pathname);
|
const pathname = matchPathname(rootPathname, url.pathname, ignorePathnames);
|
||||||
|
|
||||||
if (!pathname || ignorePathnames?.some((prefix) => matchPathname(prefix, url.pathname))) {
|
if (!pathname) {
|
||||||
return next(context);
|
return next(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import { createQueryClient } from '@withtyped/postgres';
|
import { createQueryClient } from '@withtyped/postgres';
|
||||||
|
|
||||||
|
import { EnvSet } from '#src/env-set/index.js';
|
||||||
import { parseDsn } from '#src/utils/postgres.js';
|
import { parseDsn } from '#src/utils/postgres.js';
|
||||||
|
|
||||||
export const client = createQueryClient(parseDsn(process.env.DB_URL));
|
export const client = createQueryClient(parseDsn(EnvSet.global.dbUrl));
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
import { createRouter } from '@withtyped/server';
|
import { createRouter } from '@withtyped/server';
|
||||||
|
|
||||||
|
import type { WithAuthContext } from '#src/middleware/with-auth.js';
|
||||||
|
|
||||||
import { tenants } from './tenants.js';
|
import { tenants } from './tenants.js';
|
||||||
|
|
||||||
const router = createRouter('/api').pack(tenants);
|
const router = createRouter<WithAuthContext, '/api'>('/api').pack(tenants);
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
|
|
|
@ -1,17 +1,18 @@
|
||||||
import { Router } from '@withtyped/server';
|
import { createRouter } from '@withtyped/server';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
||||||
|
import type { WithAuthContext } from '#src/middleware/with-auth.js';
|
||||||
import { client } from '#src/queries/index.js';
|
import { client } from '#src/queries/index.js';
|
||||||
import { createTenantsQueries } from '#src/queries/tenants.js';
|
import { createTenantsQueries } from '#src/queries/tenants.js';
|
||||||
import { getTenantIdFromManagementApiIndicator } from '#src/utils/tenant.js';
|
import { getTenantIdFromManagementApiIndicator } from '#src/utils/tenant.js';
|
||||||
|
|
||||||
const { getManagementApiLikeIndicatorsForUser } = createTenantsQueries(client);
|
const { getManagementApiLikeIndicatorsForUser } = createTenantsQueries(client);
|
||||||
|
|
||||||
export const tenants = new Router('/tenants').get(
|
export const tenants = createRouter<WithAuthContext, '/tenants'>('/tenants').get(
|
||||||
'/',
|
'/',
|
||||||
{ response: z.object({ id: z.string(), indicator: z.string() }).array() },
|
{ response: z.object({ id: z.string(), indicator: z.string() }).array() },
|
||||||
async (context, next) => {
|
async (context, next) => {
|
||||||
const { rows } = await getManagementApiLikeIndicatorsForUser('some_user_id');
|
const { rows } = await getManagementApiLikeIndicatorsForUser(context.auth.id);
|
||||||
|
|
||||||
const tenants = rows
|
const tenants = rows
|
||||||
.map(({ indicator }) => ({
|
.map(({ indicator }) => ({
|
||||||
|
|
|
@ -6,10 +6,18 @@ export const normalizePath = (pathLike: string) => {
|
||||||
return value.length > 1 && value.endsWith('/') ? value.slice(0, -1) : value;
|
return value.length > 1 && value.endsWith('/') ? value.slice(0, -1) : value;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const matchPathname = (toMatch: string, pathname: string) => {
|
export const matchPathname = (
|
||||||
|
toMatch: string,
|
||||||
|
pathname: string,
|
||||||
|
ignorePathnames: string[] = []
|
||||||
|
) => {
|
||||||
const toMatchPathname = normalizePath(toMatch);
|
const toMatchPathname = normalizePath(toMatch);
|
||||||
const normalized = normalizePath(pathname);
|
const normalized = normalizePath(pathname);
|
||||||
|
|
||||||
|
if (ignorePathnames.some((prefix) => matchPathname(prefix, pathname))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (normalized === toMatchPathname) {
|
if (normalized === toMatchPathname) {
|
||||||
return '/';
|
return '/';
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,8 +35,8 @@
|
||||||
"@logto/schemas": "workspace:*",
|
"@logto/schemas": "workspace:*",
|
||||||
"@logto/shared": "workspace:*",
|
"@logto/shared": "workspace:*",
|
||||||
"@silverhand/essentials": "2.2.0",
|
"@silverhand/essentials": "2.2.0",
|
||||||
"@withtyped/postgres": "^0.6.0",
|
"@withtyped/postgres": "^0.7.0",
|
||||||
"@withtyped/server": "^0.6.0",
|
"@withtyped/server": "^0.7.0",
|
||||||
"chalk": "^5.0.0",
|
"chalk": "^5.0.0",
|
||||||
"clean-deep": "^3.4.0",
|
"clean-deep": "^3.4.0",
|
||||||
"date-fns": "^2.29.3",
|
"date-fns": "^2.29.3",
|
||||||
|
|
|
@ -53,6 +53,6 @@
|
||||||
},
|
},
|
||||||
"prettier": "@silverhand/eslint-config/.prettierrc",
|
"prettier": "@silverhand/eslint-config/.prettierrc",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@withtyped/server": "^0.6.0"
|
"@withtyped/server": "^0.7.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -84,7 +84,7 @@
|
||||||
"@logto/language-kit": "workspace:*",
|
"@logto/language-kit": "workspace:*",
|
||||||
"@logto/phrases": "workspace:*",
|
"@logto/phrases": "workspace:*",
|
||||||
"@logto/phrases-ui": "workspace:*",
|
"@logto/phrases-ui": "workspace:*",
|
||||||
"@withtyped/server": "^0.6.0",
|
"@withtyped/server": "^0.7.0",
|
||||||
"zod": "^3.20.2"
|
"zod": "^3.20.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
1
packages/schemas/src/consts/cloud.ts
Normal file
1
packages/schemas/src/consts/cloud.ts
Normal file
|
@ -0,0 +1 @@
|
||||||
|
export const cloudApiIndicator = 'https://cloud.logto.io/api';
|
1
packages/schemas/src/consts/index.ts
Normal file
1
packages/schemas/src/consts/index.ts
Normal file
|
@ -0,0 +1 @@
|
||||||
|
export * from './cloud.js';
|
|
@ -3,3 +3,4 @@ export * from './db-entries/index.js';
|
||||||
export * from './types/index.js';
|
export * from './types/index.js';
|
||||||
export * from './api/index.js';
|
export * from './api/index.js';
|
||||||
export * from './seeds/index.js';
|
export * from './seeds/index.js';
|
||||||
|
export * from './consts/index.js';
|
||||||
|
|
|
@ -114,13 +114,14 @@ importers:
|
||||||
'@types/http-proxy': ^1.17.9
|
'@types/http-proxy': ^1.17.9
|
||||||
'@types/mime-types': ^2.1.1
|
'@types/mime-types': ^2.1.1
|
||||||
'@types/node': ^18.11.18
|
'@types/node': ^18.11.18
|
||||||
'@withtyped/postgres': ^0.6.0
|
'@withtyped/postgres': ^0.7.0
|
||||||
'@withtyped/server': ^0.6.0
|
'@withtyped/server': ^0.7.0
|
||||||
chalk: ^5.0.0
|
chalk: ^5.0.0
|
||||||
dotenv: ^16.0.0
|
dotenv: ^16.0.0
|
||||||
eslint: ^8.21.0
|
eslint: ^8.21.0
|
||||||
find-up: ^6.3.0
|
find-up: ^6.3.0
|
||||||
http-proxy: ^1.18.1
|
http-proxy: ^1.18.1
|
||||||
|
jose: ^4.11.0
|
||||||
lint-staged: ^13.0.0
|
lint-staged: ^13.0.0
|
||||||
mime-types: ^2.1.35
|
mime-types: ^2.1.35
|
||||||
nodemon: ^2.0.19
|
nodemon: ^2.0.19
|
||||||
|
@ -131,12 +132,13 @@ importers:
|
||||||
'@logto/schemas': link:../schemas
|
'@logto/schemas': link:../schemas
|
||||||
'@logto/shared': link:../shared
|
'@logto/shared': link:../shared
|
||||||
'@silverhand/essentials': 2.2.0
|
'@silverhand/essentials': 2.2.0
|
||||||
'@withtyped/postgres': 0.6.0_@withtyped+server@0.6.0
|
'@withtyped/postgres': 0.7.0_@withtyped+server@0.7.0
|
||||||
'@withtyped/server': 0.6.0
|
'@withtyped/server': 0.7.0
|
||||||
chalk: 5.1.2
|
chalk: 5.1.2
|
||||||
dotenv: 16.0.0
|
dotenv: 16.0.0
|
||||||
find-up: 6.3.0
|
find-up: 6.3.0
|
||||||
http-proxy: 1.18.1
|
http-proxy: 1.18.1
|
||||||
|
jose: 4.11.1
|
||||||
mime-types: 2.1.35
|
mime-types: 2.1.35
|
||||||
zod: 3.20.2
|
zod: 3.20.2
|
||||||
devDependencies:
|
devDependencies:
|
||||||
|
@ -329,8 +331,8 @@ importers:
|
||||||
'@types/semver': ^7.3.12
|
'@types/semver': ^7.3.12
|
||||||
'@types/sinon': ^10.0.13
|
'@types/sinon': ^10.0.13
|
||||||
'@types/supertest': ^2.0.11
|
'@types/supertest': ^2.0.11
|
||||||
'@withtyped/postgres': ^0.6.0
|
'@withtyped/postgres': ^0.7.0
|
||||||
'@withtyped/server': ^0.6.0
|
'@withtyped/server': ^0.7.0
|
||||||
chalk: ^5.0.0
|
chalk: ^5.0.0
|
||||||
clean-deep: ^3.4.0
|
clean-deep: ^3.4.0
|
||||||
copyfiles: ^2.4.1
|
copyfiles: ^2.4.1
|
||||||
|
@ -389,8 +391,8 @@ importers:
|
||||||
'@logto/schemas': link:../schemas
|
'@logto/schemas': link:../schemas
|
||||||
'@logto/shared': link:../shared
|
'@logto/shared': link:../shared
|
||||||
'@silverhand/essentials': 2.2.0
|
'@silverhand/essentials': 2.2.0
|
||||||
'@withtyped/postgres': 0.6.0_@withtyped+server@0.6.0
|
'@withtyped/postgres': 0.7.0_@withtyped+server@0.7.0
|
||||||
'@withtyped/server': 0.6.0
|
'@withtyped/server': 0.7.0
|
||||||
chalk: 5.1.2
|
chalk: 5.1.2
|
||||||
clean-deep: 3.4.0
|
clean-deep: 3.4.0
|
||||||
date-fns: 2.29.3
|
date-fns: 2.29.3
|
||||||
|
@ -538,7 +540,7 @@ importers:
|
||||||
'@types/jest': ^29.1.2
|
'@types/jest': ^29.1.2
|
||||||
'@types/jest-environment-puppeteer': ^5.0.2
|
'@types/jest-environment-puppeteer': ^5.0.2
|
||||||
'@types/node': ^18.11.18
|
'@types/node': ^18.11.18
|
||||||
'@withtyped/server': ^0.6.0
|
'@withtyped/server': ^0.7.0
|
||||||
dotenv: ^16.0.0
|
dotenv: ^16.0.0
|
||||||
eslint: ^8.34.0
|
eslint: ^8.34.0
|
||||||
got: ^12.5.3
|
got: ^12.5.3
|
||||||
|
@ -552,7 +554,7 @@ importers:
|
||||||
text-encoder: ^0.0.4
|
text-encoder: ^0.0.4
|
||||||
typescript: ^4.9.4
|
typescript: ^4.9.4
|
||||||
dependencies:
|
dependencies:
|
||||||
'@withtyped/server': 0.6.0
|
'@withtyped/server': 0.7.0
|
||||||
devDependencies:
|
devDependencies:
|
||||||
'@jest/types': 29.1.2
|
'@jest/types': 29.1.2
|
||||||
'@logto/connector-kit': link:../toolkit/connector-kit
|
'@logto/connector-kit': link:../toolkit/connector-kit
|
||||||
|
@ -645,7 +647,7 @@ importers:
|
||||||
'@types/jest': ^29.1.2
|
'@types/jest': ^29.1.2
|
||||||
'@types/node': ^18.11.18
|
'@types/node': ^18.11.18
|
||||||
'@types/pluralize': ^0.0.29
|
'@types/pluralize': ^0.0.29
|
||||||
'@withtyped/server': ^0.6.0
|
'@withtyped/server': ^0.7.0
|
||||||
camelcase: ^7.0.0
|
camelcase: ^7.0.0
|
||||||
eslint: ^8.34.0
|
eslint: ^8.34.0
|
||||||
jest: ^29.1.2
|
jest: ^29.1.2
|
||||||
|
@ -663,7 +665,7 @@ importers:
|
||||||
'@logto/language-kit': link:../toolkit/language-kit
|
'@logto/language-kit': link:../toolkit/language-kit
|
||||||
'@logto/phrases': link:../phrases
|
'@logto/phrases': link:../phrases
|
||||||
'@logto/phrases-ui': link:../phrases-ui
|
'@logto/phrases-ui': link:../phrases-ui
|
||||||
'@withtyped/server': 0.6.0
|
'@withtyped/server': 0.7.0
|
||||||
zod: 3.20.2
|
zod: 3.20.2
|
||||||
devDependencies:
|
devDependencies:
|
||||||
'@silverhand/eslint-config': 2.0.1_kjzxg5porcw5dx54sezsklj5cy
|
'@silverhand/eslint-config': 2.0.1_kjzxg5porcw5dx54sezsklj5cy
|
||||||
|
@ -4569,21 +4571,21 @@ packages:
|
||||||
eslint-visitor-keys: 3.3.0
|
eslint-visitor-keys: 3.3.0
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/@withtyped/postgres/0.6.0_@withtyped+server@0.6.0:
|
/@withtyped/postgres/0.7.0_@withtyped+server@0.7.0:
|
||||||
resolution: {integrity: sha512-Mq4/beT7vqtaxbNeFpP2mananch9OauwbQdvNR8YVaoolgPycxGuTB0LvnsLa4/7r4KQORQJcCxj+fckqkOFwA==}
|
resolution: {integrity: sha512-D6bI+ols0mtNvaTUp4IzNHAQtbqdakNTZgQ0E0KbVMvdfq0fhOFUaTKANRPjMs4rsyfAETfPzjt3B/Ij47ZiMA==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@withtyped/server': ^0.6.0
|
'@withtyped/server': ^0.7.0
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/pg': 8.6.6
|
'@types/pg': 8.6.6
|
||||||
'@withtyped/server': 0.6.0
|
'@withtyped/server': 0.7.0
|
||||||
'@withtyped/shared': 0.2.0
|
'@withtyped/shared': 0.2.0
|
||||||
pg: 8.8.0
|
pg: 8.8.0
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- pg-native
|
- pg-native
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/@withtyped/server/0.6.0:
|
/@withtyped/server/0.7.0:
|
||||||
resolution: {integrity: sha512-p4rlk2EIq1zjQnwDe6cDNGl3VYJQL+sSxgoCfn9wqinExQ1ReegwujOBXaBuk/LQZ0HtSqDV0ayIE/NB8AQZew==}
|
resolution: {integrity: sha512-UVW6cOJyOBDfGiSoMg2asYsKqmzL7+UPaYwqW+oxZtvlUabaCekVKTBH3l8tm7zhiOluZg9FD78t0DVuEyQTMw==}
|
||||||
dependencies:
|
dependencies:
|
||||||
'@withtyped/shared': 0.2.0
|
'@withtyped/shared': 0.2.0
|
||||||
dev: false
|
dev: false
|
||||||
|
|
Loading…
Reference in a new issue