0
Fork 0
mirror of https://github.com/logto-io/logto.git synced 2024-12-16 20:26:19 -05:00

api: register with username / password

This commit is contained in:
Gao Sun 2021-07-04 17:41:46 +08:00
parent b2c59b4e8e
commit 916130b5fc
No known key found for this signature in database
GPG key ID: 0F0EFA2E36639F31
6 changed files with 103 additions and 11 deletions

View file

@ -13,7 +13,7 @@
},
"dependencies": {
"@logto/essentials": "^1.0.5",
"@logto/schemas": "^1.0.3",
"@logto/schemas": "^1.0.5",
"dayjs": "^1.10.5",
"dotenv": "^10.0.0",
"got": "^11.8.2",
@ -24,6 +24,7 @@
"koa-proxies": "^0.12.1",
"koa-router": "^10.0.0",
"module-alias": "^2.2.2",
"nanoid": "^3.1.23",
"oidc-provider": "^7.4.1",
"slonik": "^23.8.1",
"slonik-interceptor-preset": "^1.2.10",

View file

@ -1,4 +1,4 @@
import { IdentifierSqlTokenType, sql } from 'slonik';
import { IdentifierSqlTokenType, sql, ValueExpressionType } from 'slonik';
type Table = { table: string; fields: Record<string, string> };
type FieldIdentifiers<Key extends string | number | symbol> = {
@ -19,3 +19,15 @@ export const convertToIdentifiers = <T extends Table>(
{} as FieldIdentifiers<keyof T['fields']>
),
});
export const insertInto = <T extends string>(
table: IdentifierSqlTokenType,
fields: FieldIdentifiers<T>,
fieldKeys: readonly T[],
value: { [key in T]?: ValueExpressionType }
) => sql`
insert into ${table} (${sql.join(Object.values(fields), sql`, `)})
values (${sql.join(
fieldKeys.map((key) => value[key] ?? null),
sql`, `
)})`;

View file

@ -3,11 +3,13 @@ import Router from 'koa-router';
import { Provider } from 'oidc-provider';
import createSignInRoutes from '@/routes/sign-in';
import createUIProxy from '@/proxies/ui';
import createRegisterRoutes from '@/routes/register';
const createRouter = (provider: Provider): Router => {
const router = new Router();
const router = new Router({ prefix: '/api' });
router.use('/api', createSignInRoutes(provider));
router.use(createSignInRoutes(provider));
router.use(createRegisterRoutes());
return router;
};

View file

@ -1,7 +1,7 @@
import { UserDBEntry, Users } from '@logto/schemas';
import { sql } from 'slonik';
import pool from '@/database/pool';
import { convertToIdentifiers } from '@/database/utils';
import { convertToIdentifiers, insertInto } from '@/database/utils';
const { table, fields } = convertToIdentifiers(Users);
@ -9,5 +9,22 @@ export const findUserById = async (id: string) =>
pool.one<UserDBEntry>(sql`
select ${sql.join(Object.values(fields), sql`,`)}
from ${table}
where id=${id}
where ${fields.id}=${id}
`);
export const hasUser = async (username: string) =>
pool.exists(sql`
select ${fields.id}
from ${table}
where ${fields.username}=${username}
`);
export const hasUserWithId = async (id: string) =>
pool.exists(sql`
select ${fields.id}
from ${table}
where ${fields.id}=${id}
`);
export const insertUser = async (user: UserDBEntry) =>
pool.query(insertInto(table, fields, Users.fieldKeys, user));

View file

@ -0,0 +1,60 @@
import Router from 'koa-router';
import koaBody from 'koa-body';
import { object, string } from 'zod';
import { encryptPassword } from '@/utils/password';
import { hasUser, hasUserWithId, insertUser } from '@/queries/user';
import { customAlphabet, nanoid } from 'nanoid';
import { PasswordEncryptionMethod } from '@logto/schemas';
const alphabet = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
const userId = customAlphabet(alphabet, 12);
const generateUserId = async (maxRetries = 500) => {
for (let i = 0; i < maxRetries; ++i) {
const id = userId();
// eslint-disable-next-line no-await-in-loop
if (!(await hasUserWithId(id))) {
return id;
}
}
throw new Error('Cannot generate user ID in reasonable retries');
};
export default function createRegisterRoutes() {
const router = new Router();
router.post('/register', koaBody(), async (ctx) => {
const RegisterBody = object({
username: string().min(3),
password: string().min(6),
});
const { username, password } = RegisterBody.parse(ctx.request.body);
if (await hasUser(username)) {
throw new Error('Username already exists');
}
const id = await generateUserId();
const passwordEncryptionSalt = nanoid();
const passwordEncryptionMethod = PasswordEncryptionMethod.SaltAndPepper;
const passwordEncrypted = encryptPassword(
id,
password,
passwordEncryptionSalt,
passwordEncryptionMethod
);
await insertUser({
id,
username,
passwordEncrypted,
passwordEncryptionMethod,
passwordEncryptionSalt,
});
ctx.body = { id };
});
return router.routes();
}

View file

@ -396,10 +396,10 @@
lodash.orderby "^4.6.0"
lodash.pick "^4.4.0"
"@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==
"@logto/schemas@^1.0.5":
version "1.0.5"
resolved "https://registry.yarnpkg.com/@logto/schemas/-/schemas-1.0.5.tgz#7784ca8f58569bfe1feafb4ad5ca6f098b1a557e"
integrity sha512-JxA9uXNz2tt0NqAp8Gl0hp0L/dLhZakG5sm41JrJKfhtse2Yy2K1X7gBYkH1C9P8P+NcHhv/K4pEgY66yXMOGQ==
"@mrmlnc/readdir-enhanced@^2.2.1":
version "2.2.1"
@ -3870,7 +3870,7 @@ multimap@^1.1.0:
resolved "https://registry.yarnpkg.com/multimap/-/multimap-1.1.0.tgz#5263febc085a1791c33b59bb3afc6a76a2a10ca8"
integrity sha512-0ZIR9PasPxGXmRsEF8jsDzndzHDj7tIav+JUmvIFB/WHswliFnquxECT/De7GR4yg99ky/NlRKJT82G1y271bw==
nanoid@^3.1.15:
nanoid@^3.1.15, nanoid@^3.1.23:
version "3.1.23"
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.1.23.tgz#f744086ce7c2bc47ee0a8472574d5c78e4183a81"
integrity sha512-FiB0kzdP0FFVGDKlRLEQ1BgDzU87dy5NnzjeW9YZNt+/c3+q82EQDUwniSAUxp/F0gFNI1ZhKU1FqYsMuqZVnw==