0
Fork 0
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:
Gao Sun 2021-07-03 21:19:20 +08:00
parent b1decc3706
commit f419a91c5d
No known key found for this signature in database
GPG key ID: 0F0EFA2E36639F31
12 changed files with 181 additions and 30 deletions

View file

@ -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",

View file

@ -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');

View file

@ -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()];

View file

@ -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'));

View file

@ -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}`);

View file

@ -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;
}

View file

@ -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;
}

View 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();
}

View 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();
}

View 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;
};

View file

@ -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==