mirror of
https://github.com/logto-io/logto.git
synced 2024-12-16 20:26:19 -05:00
feat: sign in API via user id + password
This commit is contained in:
parent
b1decc3706
commit
f419a91c5d
12 changed files with 181 additions and 30 deletions
|
@ -13,18 +13,20 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"@logto/essentials": "^1.0.5",
|
||||
"@logto/schemas": "^1.0.1",
|
||||
"@logto/schemas": "^1.0.3",
|
||||
"dayjs": "^1.10.5",
|
||||
"dotenv": "^10.0.0",
|
||||
"got": "^11.8.2",
|
||||
"koa": "^2.13.1",
|
||||
"koa-body": "^4.2.0",
|
||||
"koa-logger": "^3.2.1",
|
||||
"koa-mount": "^4.0.0",
|
||||
"koa-router": "^10.0.0",
|
||||
"module-alias": "^2.2.2",
|
||||
"oidc-provider": "^7.4.1",
|
||||
"slonik": "^23.8.1",
|
||||
"slonik-interceptor-preset": "^1.2.10"
|
||||
"slonik-interceptor-preset": "^1.2.10",
|
||||
"zod": "^3.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@commitlint/cli": "^12.1.4",
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
import { assertEnv } from './utils';
|
||||
import { assertEnv } from '@/utils/env';
|
||||
|
||||
export const signInRoute = assertEnv('SIGN_IN_ROUTE');
|
||||
export const signInRoute = assertEnv('UI_SIGN_IN_ROUTE');
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { createPool } from 'slonik';
|
||||
import { createInterceptors } from 'slonik-interceptor-preset';
|
||||
import { getEnv } from '@/utils';
|
||||
import { getEnv } from '@/utils/env';
|
||||
|
||||
const interceptors = [...createInterceptors()];
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@ dotenv.config();
|
|||
|
||||
import Koa from 'koa';
|
||||
import initApp from './init';
|
||||
import { getEnv } from './utils';
|
||||
import { getEnv } from './utils/env';
|
||||
|
||||
const app = new Koa();
|
||||
const port = Number(getEnv('PORT', '3001'));
|
||||
|
|
|
@ -6,8 +6,9 @@ import initRouter from './router';
|
|||
|
||||
export default async function initApp(app: Koa, port: number): Promise<void> {
|
||||
app.use(logger());
|
||||
await initOidc(app, port);
|
||||
initRouter(app);
|
||||
|
||||
const provider = await initOidc(app, port);
|
||||
initRouter(app, provider);
|
||||
|
||||
app.listen(port, () => {
|
||||
console.log(`App is listening on port ${port}`);
|
||||
|
|
|
@ -5,11 +5,11 @@ import { Provider } from 'oidc-provider';
|
|||
import postgresAdapter from '@/oidc/adapter';
|
||||
|
||||
import { fromKeyLike } from 'jose/jwk/from_key_like';
|
||||
import { getEnv } from '@/utils';
|
||||
import { getEnv } from '@/utils/env';
|
||||
import { findUserById } from '@/queries/user';
|
||||
import { signInRoute } from '@/consts';
|
||||
|
||||
export default async function initOidc(app: Koa, port: number): Promise<void> {
|
||||
export default async function initOidc(app: Koa, port: number): Promise<Provider> {
|
||||
const privateKey = crypto.createPrivateKey(
|
||||
Buffer.from(getEnv('OIDC_PROVIDER_PRIVATE_KEY_BASE64'), 'base64')
|
||||
);
|
||||
|
@ -41,7 +41,7 @@ export default async function initOidc(app: Koa, port: number): Promise<void> {
|
|||
devInteractions: { enabled: false },
|
||||
},
|
||||
interactions: {
|
||||
url: (_, interaction) => `${signInRoute}?uid=${interaction.uid}`,
|
||||
url: (_) => signInRoute,
|
||||
},
|
||||
clientBasedCORS: (_, origin) => {
|
||||
console.log('origin', origin);
|
||||
|
@ -63,4 +63,5 @@ export default async function initOidc(app: Koa, port: number): Promise<void> {
|
|||
},
|
||||
});
|
||||
app.use(mount('/oidc', oidc.app));
|
||||
return oidc;
|
||||
}
|
||||
|
|
|
@ -1,19 +1,20 @@
|
|||
import got from 'got';
|
||||
import Koa from 'koa';
|
||||
import Router from 'koa-router';
|
||||
import { promisify } from 'util';
|
||||
import stream from 'stream';
|
||||
import { signInRoute } from '@/consts';
|
||||
import { getEnv } from '@/utils';
|
||||
import { Provider } from 'oidc-provider';
|
||||
import createSignInRoutes from '@/routes/sign-in';
|
||||
import createUIRoutes from '@/routes/ui';
|
||||
|
||||
const pipeline = promisify(stream.pipeline);
|
||||
const router = new Router();
|
||||
const createRouter = (provider: Provider): Router => {
|
||||
const router = new Router();
|
||||
|
||||
router.get(new RegExp(`^${signInRoute}(?:/|$)`), async (ctx) => {
|
||||
// CAUTION: this is for dev purpose only, add a switch if needed
|
||||
await pipeline(got.stream.get(getEnv('PLAYGROUND_URL')), ctx.res);
|
||||
});
|
||||
router.use('/api', createSignInRoutes());
|
||||
router.use(createUIRoutes(provider));
|
||||
|
||||
export default function initRouter(app: Koa): void {
|
||||
return router;
|
||||
};
|
||||
|
||||
export default function initRouter(app: Koa, provider: Provider): Router {
|
||||
const router = createRouter(provider);
|
||||
app.use(router.routes()).use(router.allowedMethods());
|
||||
return router;
|
||||
}
|
||||
|
|
30
packages/core/src/routes/sign-in.ts
Normal file
30
packages/core/src/routes/sign-in.ts
Normal file
|
@ -0,0 +1,30 @@
|
|||
import assert from 'assert';
|
||||
import Router from 'koa-router';
|
||||
import koaBody from 'koa-body';
|
||||
import { object, string } from 'zod';
|
||||
import { encryptPassword } from '@/utils/password';
|
||||
import { findUserById } from '@/queries/user';
|
||||
|
||||
export default function createSignInRoutes() {
|
||||
const router = new Router();
|
||||
|
||||
router.post('/sign-in', koaBody(), async (ctx) => {
|
||||
const SignInBody = object({
|
||||
id: string().min(1),
|
||||
password: string().min(1),
|
||||
});
|
||||
const { id, password } = SignInBody.parse(ctx.request.body);
|
||||
const { passwordEncrypted, passwordEncryptionMethod, passwordEncryptionSalt } =
|
||||
await findUserById(id);
|
||||
|
||||
assert(passwordEncrypted && passwordEncryptionMethod && passwordEncryptionSalt);
|
||||
assert(
|
||||
encryptPassword(id, password, passwordEncryptionSalt, passwordEncryptionMethod) ===
|
||||
passwordEncrypted
|
||||
);
|
||||
|
||||
ctx.status = 204;
|
||||
});
|
||||
|
||||
return router.routes();
|
||||
}
|
20
packages/core/src/routes/ui.ts
Normal file
20
packages/core/src/routes/ui.ts
Normal file
|
@ -0,0 +1,20 @@
|
|||
import got from 'got';
|
||||
import Router from 'koa-router';
|
||||
import { promisify } from 'util';
|
||||
import stream from 'stream';
|
||||
import { signInRoute } from '@/consts';
|
||||
import { getEnv } from '@/utils/env';
|
||||
import { Provider } from 'oidc-provider';
|
||||
|
||||
export default function createUIRoutes(provider: Provider) {
|
||||
const pipeline = promisify(stream.pipeline);
|
||||
const router = new Router();
|
||||
|
||||
router.get(new RegExp(`^${signInRoute}(?:/|$)`), async (ctx) => {
|
||||
const details = await provider.interactionDetails(ctx.req, ctx.res);
|
||||
console.log('details', details);
|
||||
// CAUTION: this is for dev purpose only, add a switch if needed
|
||||
await pipeline(got.stream.get(getEnv('UI_PLAYGROUND_URL')), ctx.res);
|
||||
});
|
||||
return router.routes();
|
||||
}
|
39
packages/core/src/utils/password.ts
Normal file
39
packages/core/src/utils/password.ts
Normal file
|
@ -0,0 +1,39 @@
|
|||
import assert from 'assert';
|
||||
import { createHash } from 'crypto';
|
||||
import { PasswordEncryptionMethod } from '@logto/schemas';
|
||||
import { number, string } from 'zod';
|
||||
import { assertEnv } from './env';
|
||||
|
||||
const peppers = string()
|
||||
.array()
|
||||
.parse(JSON.parse(assertEnv('PASSWORD_PEPPERS')));
|
||||
const iterationCount = number()
|
||||
.min(100)
|
||||
.parse(Number(assertEnv('PASSWORD_INTERATION_COUNT')));
|
||||
|
||||
export const encryptPassword = (
|
||||
id: string,
|
||||
password: string,
|
||||
salt: string,
|
||||
method: PasswordEncryptionMethod
|
||||
): string => {
|
||||
assert(
|
||||
method === PasswordEncryptionMethod.SaltAndPepper,
|
||||
'Unsupported password encryption method'
|
||||
);
|
||||
|
||||
const sum = [...id].reduce((acc, current) => acc + current.charCodeAt(0), 0);
|
||||
const pepper = peppers[sum % peppers.length];
|
||||
|
||||
assert(pepper, 'Password pepper not found');
|
||||
|
||||
let result = password;
|
||||
|
||||
for (let i = 0; i < iterationCount; ++i) {
|
||||
result = createHash('sha256')
|
||||
.update(salt + result + pepper)
|
||||
.digest('hex');
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
|
@ -396,10 +396,10 @@
|
|||
lodash.orderby "^4.6.0"
|
||||
lodash.pick "^4.4.0"
|
||||
|
||||
"@logto/schemas@^1.0.1":
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@logto/schemas/-/schemas-1.0.1.tgz#786866a280568b275b9c55c2b884b5864dc42a06"
|
||||
integrity sha512-SwAz/rOE61RDBU3TzsDqO30vkLRgD/Jcc3H3tC40gl39wTfxWiTrXgGtWTxAhrhdTwstEMa5+5ozczP/LvopwQ==
|
||||
"@logto/schemas@^1.0.3":
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/@logto/schemas/-/schemas-1.0.3.tgz#6a596bb9d1b99460857eaec5d643040ae384e037"
|
||||
integrity sha512-inCo/PQUQl9OZFkjehjcB/4PBzYBW9FVn/3nZ2CPr/+3dJKDF3B0qxu8gyQICGJuWYx1BZPygc/NvV0F1klR3g==
|
||||
|
||||
"@mrmlnc/readdir-enhanced@^2.2.1":
|
||||
version "2.2.1"
|
||||
|
@ -538,6 +538,13 @@
|
|||
"@types/qs" "*"
|
||||
"@types/serve-static" "*"
|
||||
|
||||
"@types/formidable@^1.0.31":
|
||||
version "1.2.2"
|
||||
resolved "https://registry.yarnpkg.com/@types/formidable/-/formidable-1.2.2.tgz#e690d60732ee9d3f0a441bc572c17409785b283c"
|
||||
integrity sha512-8RDAMnMHOh7QrY1xuQ7s6/Xre9pMvJ2zT2VgATiz5cIE71Q/6N3+P8sr3z/dNWNmvX5/aX9x8uJlG0MZiMZXoA==
|
||||
dependencies:
|
||||
"@types/node" "*"
|
||||
|
||||
"@types/glob@^7.1.1":
|
||||
version "7.1.3"
|
||||
resolved "https://registry.yarnpkg.com/@types/glob/-/glob-7.1.3.tgz#e6ba80f36b7daad2c685acd9266382e68985c183"
|
||||
|
@ -1276,6 +1283,16 @@ clone-response@^1.0.2:
|
|||
dependencies:
|
||||
mimic-response "^1.0.0"
|
||||
|
||||
co-body@^5.1.1:
|
||||
version "5.2.0"
|
||||
resolved "https://registry.yarnpkg.com/co-body/-/co-body-5.2.0.tgz#5a0a658c46029131e0e3a306f67647302f71c124"
|
||||
integrity sha512-sX/LQ7LqUhgyaxzbe7IqwPeTr2yfpfUIQ/dgpKo6ZI4y4lpQA0YxAomWIY+7I7rHWcG02PG+OuPREzMW/5tszQ==
|
||||
dependencies:
|
||||
inflation "^2.0.0"
|
||||
qs "^6.4.0"
|
||||
raw-body "^2.2.0"
|
||||
type-is "^1.6.14"
|
||||
|
||||
co@^4.6.0:
|
||||
version "4.6.0"
|
||||
resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184"
|
||||
|
@ -2374,6 +2391,11 @@ for-in@^1.0.2:
|
|||
resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80"
|
||||
integrity sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=
|
||||
|
||||
formidable@^1.1.1:
|
||||
version "1.2.2"
|
||||
resolved "https://registry.yarnpkg.com/formidable/-/formidable-1.2.2.tgz#bf69aea2972982675f00865342b982986f6b8dd9"
|
||||
integrity sha512-V8gLm+41I/8kguQ4/o1D3RIHRmhYFG4pnNyonvua+40rqcEmT4+V71yaZ3B457xbbgCsCfjSPi65u/W6vK1U5Q==
|
||||
|
||||
fragment-cache@^0.2.1:
|
||||
version "0.2.1"
|
||||
resolved "https://registry.yarnpkg.com/fragment-cache/-/fragment-cache-0.2.1.tgz#4290fad27f13e89be7f33799c6bc5a0abfff0d19"
|
||||
|
@ -2814,6 +2836,11 @@ indent-string@^4.0.0:
|
|||
resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-4.0.0.tgz#624f8f4497d619b2d9768531d58f4122854d7251"
|
||||
integrity sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==
|
||||
|
||||
inflation@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/inflation/-/inflation-2.0.0.tgz#8b417e47c28f925a45133d914ca1fd389107f30f"
|
||||
integrity sha1-i0F+R8KPklpFEz2RTKH9OJEH8w8=
|
||||
|
||||
inflight@^1.0.4:
|
||||
version "1.0.6"
|
||||
resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9"
|
||||
|
@ -3375,6 +3402,15 @@ kind-of@^6.0.0, kind-of@^6.0.2, kind-of@^6.0.3:
|
|||
resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd"
|
||||
integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==
|
||||
|
||||
koa-body@^4.2.0:
|
||||
version "4.2.0"
|
||||
resolved "https://registry.yarnpkg.com/koa-body/-/koa-body-4.2.0.tgz#37229208b820761aca5822d14c5fc55cee31b26f"
|
||||
integrity sha512-wdGu7b9amk4Fnk/ytH8GuWwfs4fsB5iNkY8kZPpgQVb04QZSv85T0M8reb+cJmvLE8cjPYvBzRikD3s6qz8OoA==
|
||||
dependencies:
|
||||
"@types/formidable" "^1.0.31"
|
||||
co-body "^5.1.1"
|
||||
formidable "^1.1.1"
|
||||
|
||||
koa-compose@^3.0.0:
|
||||
version "3.2.1"
|
||||
resolved "https://registry.yarnpkg.com/koa-compose/-/koa-compose-3.2.1.tgz#a85ccb40b7d986d8e5a345b3a1ace8eabcf54de7"
|
||||
|
@ -3882,7 +3918,7 @@ object-hash@^2.0.3:
|
|||
resolved "https://registry.yarnpkg.com/object-hash/-/object-hash-2.2.0.tgz#5ad518581eefc443bd763472b8ff2e9c2c0d54a5"
|
||||
integrity sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw==
|
||||
|
||||
object-inspect@^1.10.3:
|
||||
object-inspect@^1.10.3, object-inspect@^1.9.0:
|
||||
version "1.10.3"
|
||||
resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.10.3.tgz#c2aa7d2d09f50c99375704f7a0adf24c5782d369"
|
||||
integrity sha512-e5mCJlSH7poANfC8z8S9s9S2IN5/4Zb3aZ33f5s8YqoazCFzNLloLU8r5VCG+G7WoqLvAAZoVMcy3tp/3X0Plw==
|
||||
|
@ -4486,6 +4522,13 @@ q@^1.5.1:
|
|||
resolved "https://registry.yarnpkg.com/q/-/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7"
|
||||
integrity sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc=
|
||||
|
||||
qs@^6.4.0:
|
||||
version "6.10.1"
|
||||
resolved "https://registry.yarnpkg.com/qs/-/qs-6.10.1.tgz#4931482fa8d647a5aab799c5271d2133b981fb6a"
|
||||
integrity sha512-M528Hph6wsSVOBiYUnGf+K/7w0hNshs/duGsNXPUCLH5XAqjEtiPGwNONLV0tBH8NoGb0mvD5JubnUTrujKDTg==
|
||||
dependencies:
|
||||
side-channel "^1.0.4"
|
||||
|
||||
queue-microtask@^1.2.2:
|
||||
version "1.2.3"
|
||||
resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243"
|
||||
|
@ -4501,7 +4544,7 @@ quick-lru@^5.1.1:
|
|||
resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-5.1.1.tgz#366493e6b3e42a3a6885e2e99d18f80fb7a8c932"
|
||||
integrity sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==
|
||||
|
||||
raw-body@^2.4.1:
|
||||
raw-body@^2.2.0, raw-body@^2.4.1:
|
||||
version "2.4.1"
|
||||
resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.4.1.tgz#30ac82f98bb5ae8c152e67149dac8d55153b168c"
|
||||
integrity sha512-9WmIKF6mkvA0SLmA2Knm9+qj89e+j1zqgyn8aXGd7+nAduPoqgI9lO57SAZNn/Byzo5P7JhXTyg9PzaJbH73bA==
|
||||
|
@ -4863,6 +4906,15 @@ shell-quote@^1.7.2:
|
|||
resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.7.2.tgz#67a7d02c76c9da24f99d20808fcaded0e0e04be2"
|
||||
integrity sha512-mRz/m/JVscCrkMyPqHc/bczi3OQHkLTqXHEFu0zDhK/qfv3UcOA4SVmRCLmos4bhjr9ekVQubj/R7waKapmiQg==
|
||||
|
||||
side-channel@^1.0.4:
|
||||
version "1.0.4"
|
||||
resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.4.tgz#efce5c8fdc104ee751b25c58d4290011fa5ea2cf"
|
||||
integrity sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==
|
||||
dependencies:
|
||||
call-bind "^1.0.0"
|
||||
get-intrinsic "^1.0.2"
|
||||
object-inspect "^1.9.0"
|
||||
|
||||
signal-exit@^3.0.2, signal-exit@^3.0.3:
|
||||
version "3.0.3"
|
||||
resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.3.tgz#a1410c2edd8f077b08b4e253c8eacfcaf057461c"
|
||||
|
@ -5452,7 +5504,7 @@ type-fest@^0.8.0, type-fest@^0.8.1:
|
|||
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d"
|
||||
integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==
|
||||
|
||||
type-is@^1.6.16:
|
||||
type-is@^1.6.14, type-is@^1.6.16:
|
||||
version "1.6.18"
|
||||
resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131"
|
||||
integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==
|
||||
|
@ -5792,3 +5844,8 @@ yocto-queue@^0.1.0:
|
|||
version "0.1.0"
|
||||
resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b"
|
||||
integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==
|
||||
|
||||
zod@^3.2.0:
|
||||
version "3.2.0"
|
||||
resolved "https://registry.yarnpkg.com/zod/-/zod-3.2.0.tgz#4f06fac3c74e56902eae43a47a1687bf49c9b70b"
|
||||
integrity sha512-yvcO3FZ8URR+LliMGqaW7tlVOOTzmup3vzKEe9Ds7twyJtdhvYa7dIYr0FbD1wVfWC1OuS83vZfHtCKslPuRhA==
|
||||
|
|
Loading…
Reference in a new issue