ð åæãµãŒãã¹ã®äœæ
ãã®ã¬ã€ãã§ã¯ãè€æ°ã®NodeblocksãµãŒãã¹ãåäžã®åæãµãŒãã¹ã«çµã¿åããæ¹ã玹ä»ããŸããèªèšŒãšãããã£ãŒã«ç®¡çæ©èœã1ã€ã®ã¢ããªã±ãŒã·ã§ã³ã«çµ±åããè€åAuth + ãããã£ãŒã«ãµãŒãã¹ãæ§ç¯ããŸãããã®ãã¿ãŒã³ã¯ããµãŒãã¹éã§ã³ã³ããã¹ããå ±æãããå Žåãçµ±äžãããAPIãäœæãããå Žåã«äŸ¿å©ã§ãã
ðŠ å¿ èŠãªããã±ãŒãž: ãã®äŸã§ã¯ExpressãSDKãRamdaãããã³
cookie-parserãã€ã³ããŒãããŸããã€ã³ã¹ããŒã«ããŠãã ããïŒnpm install express @nodeblocks/backend-sdk ramda cookie-parsernpm install --save-dev @types/node @types/express @types/ramda @types/cookie-parserâïž ã¢ãžã¥ãŒã«èšå®: ãã®äŸã§ã¯ãããã¬ãã«ã®
awaitã䜿çšããŸããESMã¢ãžã¥ãŒã«ãšããŠå®è¡ãããïŒäŸãã°tsconfig.jsonã§"module": "NodeNext"ãpackage.jsonã§"type": "module"ïŒãbootstrapã³ãŒããasync function main()ã«ç§»åããŠãã ãããðŒïž ã¢ãã¿ãŒãµããŒã: ãã®ãµã³ãã«ã®ãããã£ãŒã«ã«ãŒãã¯ã¢ãã¿ãŒURLãæ£èŠåããã®ã§ãäŸã§ãSDKã®
createFileStorageDriverã䜿çšããŸããð Google CloudèªèšŒæ å ±:
createFileStorageDriverã¯å éšã§Google Cloud Storageã䜿çšããã®ã§ãç°å¢ã¯Stillsigned URLçæã®ããã«æå¹ãªGoogle CloudèªèšŒæ å ±ãå¿ èŠãšããŸãã
ðïž ãµãŒãã¹ã¢ãŒããã¯ãã£â
åæãµãŒãã¹ãã¿ãŒã³ã«ããã以äžã®ããšãã§ããŸãïŒ
- è€æ°ã®ãµãŒãã¹ãçµã¿åããã - èªèšŒãšãããã£ãŒã«ç®¡çãããŒãž
- ã³ã³ããã¹ããå ±æãã - åãããŒã¿ããŒã¹èšå®ãšãªã¯ãšã¹ãã³ã³ããã¹ãã䜿çš
- çµ±äžãããããã«ãŠã§ã¢ - è€æ°ã®ãµãŒãã¹ããåäžã®Expressããã«ãŠã§ã¢ãäœæ
- ãããã€ã®ç°¡çŽ å - è€æ°ã®é¢é£ãµãŒãã¹ã1ã€ã®ã¢ããªã±ãŒã·ã§ã³ãšããŠãããã€
1ïžâ£ ã³ã³ããŒãã³ããçè§£ããâ
åæãµãŒãã¹ãæ§ç¯ããåã«ãçµã¿åããããã®ãçè§£ããŸãããïŒ
èªèšŒãµãŒãã¹ãã£ãŒãã£ãŒâ
- è³æ Œæ å ±ãç»é² - ã¡ãŒã«/ãã¹ã¯ãŒãã«ããã¢ã€ãã³ãã£ãã£ç»é²
- è³æ Œæ å ±ã§ãã°ã€ã³ - èªèšŒãšããŒã¯ã³çæ
- ãã°ã¢ãŠã - çŸåšã®ã»ãã·ã§ã³ãç¡å¹å
- ãã®ã¬ã€ãã¯æå³çã«æåŸ ãMFAãã¯ã³ã¿ã€ã ããŒã¯ã³ãã¡ãŒã«æ€èšŒããã¹ã¯ãŒããªã»ãããããã³OAuthãããŒãé€å€ããŸãã
ãããã£ãŒã«ãµãŒãã¹ãã£ãŒãã£ãŒâ
- ãããã£ãŒã«ãäœæ - ã¢ã€ãã³ãã£ãã£ã®æ°ãããããã£ãŒã«ãäœæ
- ãããã£ãŒã«ãååŸ - IDã§ãããã£ãŒã«ãååŸ
- ãããã£ãŒã«ãæŽæ° - ãããã£ãŒã«ãã£ãŒã«ããæŽæ°
- ãããã£ãŒã«ãåé€ - ãããã£ãŒã«ãåé€
- ãããã£ãŒã«ãæ€çŽ¢ - ãããã£ãŒã«ã®äžèŠ§è¡šç€ºãšãã£ã«ã¿ãŒ
- ãã®ã¬ã€ãã¯æå³çã«ã¢ãã¿ãŒã¢ããããŒãURLãã¢ã€ãã³ãã£ãã£IDã«ããæ€çŽ¢ãããã³ãã©ããŒ/ããããããŒãé€å€ããŸãã
2ïžâ£ ãµãŒãã¹ããã«ãŠã§ã¢ã®äœæâ
è€æ°ã®ãµãŒãã¹ãã£ãŒãã£ãŒãçµ±åãããããã«ãŠã§ã¢ã«çµã¿åããã compositeService.ts ãã¡ã€ã«ãäœæïŒ
import cookieParser from 'cookie-parser';
import express from 'express';
import {partial} from 'ramda';
import {middlewares, features, primitives, drivers, utils} from '@nodeblocks/backend-sdk';
const {withMongo, createFileStorageDriver} = drivers;
const {getCookieTokenInfo} = utils;
const connectToDatabase = withMongo('mongodb://localhost:27017/?authSource=admin', 'dev', 'user', 'password');
const fileStorageDriver = createFileStorageDriver(process.env.GCP_PROJECT_ID!, process.env.GCP_BUCKET_NAME!);
const {nodeBlocksErrorMiddleware} = middlewares;
const {defService, compose} = primitives;
const {
registerCredentialsFeature,
loginWithCredentialsFeature,
logoutFeature,
createProfileFeature,
getProfileFeature,
editProfileFeature,
deleteProfileFeature,
findProfilesFeature,
} = features;
const authServiceMiddleware = compose(registerCredentialsFeature, loginWithCredentialsFeature, logoutFeature);
const profileServiceMiddleware = compose(
createProfileFeature,
getProfileFeature,
editProfileFeature,
deleteProfileFeature,
findProfilesFeature,
);
const dataStores = {
...(await connectToDatabase('identities')),
...(await connectToDatabase('profiles')),
};
const configuration = {
// ãã¹ãã¢ããªãcookie-parserãç»é²ããå Žåã®ã¿ 'cookie' ã䜿çšã
authMode: (process.env.AUTH_MODE === 'cookie' ? 'cookie' : 'bearer') as 'bearer' | 'cookie',
authSecrets: {
authEncSecret: 'your-encryption-secret',
authSignSecret: 'your-signing-secret',
},
maxFailedLoginAttempts: 5,
accessTokenSignOptions: {expiresIn: '2h'},
refreshTokenSignOptions: {expiresIn: '2d'},
identity: {
typeIds: {
admin: '100',
guest: '000',
regular: '001',
},
},
};
const context = {
dataStores,
configuration,
fileStorageDriver,
// ãã£ãŒãã£ãŒåæã¯profileServiceã®ããã«èªåçã«ãããèšå®ããŸããã
...(configuration.authMode === 'cookie' && {
authenticate: getCookieTokenInfo,
}),
};
const appMiddleware = compose(authServiceMiddleware, profileServiceMiddleware);
express()
// åäžã®ããŒã ã¹ããŒã¹äžã«ãµãŒãã¹ãå®çŸ©ã§ããŸãïŒäŸïŒ/api/authã/api/profilesïŒ
.use(cookieParser())
.use('/api', defService(partial(appMiddleware, [context])))
.use(nodeBlocksErrorMiddleware())
.listen(8089, () => console.log('Server running'));
3ïžâ£ ãã¿ãŒã³ã®çè§£â
ãµãŒãã¹åæâ
åæãµãŒãã¹ã®éµã¯è€æ°ã®ãã£ãŒãã£ãŒãçµã¿åããã compose 颿°ã§ãïŒ
// åã
ã®ãµãŒãã¹ããã«ãŠã§ã¢
const authServiceMiddleware = compose(registerCredentialsFeature, loginWithCredentialsFeature, logoutFeature);
const profileServiceMiddleware = compose(
createProfileFeature,
getProfileFeature,
editProfileFeature,
deleteProfileFeature,
findProfilesFeature,
);
// çµåãããããã«ãŠã§ã¢
const appMiddleware = compose(authServiceMiddleware, profileServiceMiddleware);
å ±æã³ã³ããã¹ãâ
ãã¹ãŠã®ãµãŒãã¹ã¯åãã³ã³ããã¹ããªããžã§ã¯ããå ±æãã以äžãå«ã¿ãŸãïŒ
- 衚瀺ãããèªèšŒãšãããã£ãŒã«ã«ãŒãçšã®ããŒã¿ããŒã¹ã³ã¬ã¯ã·ã§ã³ïŒ
identitiesãšprofilesïŒ - èªèšŒã·ãŒã¯ã¬ãããšããŒã¯ã³ãªãã·ã§ã³ãå«ããã¹ãããã
configuration - ãããã£ãŒã«ã¬ã¹ãã³ã¹ã®ã¢ãã¿ãŒURLæ£èŠåçšã®
fileStorageDriver - ã¯ãããŒèªèšŒããªããã€ã³ããå Žåã®
authenticate颿°
åŸã§æåŸ
ããŒã¯ã³ç»é²ãŸãã¯MFA / ã¯ã³ã¿ã€ã ããŒã¯ã³ãã°ã€ã³ãæå¹ã«ããå ŽåãèªèšŒããŒã¿ã¹ãã¢ã« invitations ããã³/ãŸã㯠onetimetokens ã远å ããŠãã ããã
const context = {
dataStores: {
...(await connectToDatabase('identities')),
...(await connectToDatabase('profiles')),
},
configuration: {
authSecrets: {
authEncSecret: 'your-encryption-secret',
authSignSecret: 'your-signing-secret',
},
maxFailedLoginAttempts: 5,
accessTokenSignOptions: {expiresIn: '2h'},
refreshTokenSignOptions: {expiresIn: '2d'},
identity: {
typeIds: {
admin: '100',
guest: '000',
regular: '001',
},
},
},
fileStorageDriver,
};
éšåé©çšâ
Ramdaã® partial 颿°ã¯ããã«ãŠã§ã¢ã«ã³ã³ããã¹ããäºåã«é©çšããŸãïŒ
defService(partial(appMiddleware, [context]));
ããã¯Expressã§äœ¿çšã§ããããã«æºåããããµãŒãã¹ãã¡ã¯ããªãäœæããŸããã³ã³ããã¹ãã¯åžžã«1èŠçŽ ã¢ã¬ã€ãšããŠæž¡ããŠãã ããïŒ[{ dataStores, configuration, ... }]ã
4ïžâ£ APIãšã³ããã€ã³ãâ
åæãµãŒãã¹ã¯ä»¥äžã®ãšã³ããã€ã³ããå ¬éïŒ
ãã®äŸã¯ãã¢ã©ãŒããŒã¯ã³ã䜿çšããŸããã¯ãããŒèªèšŒã䜿çšããã«ã¯ãconfiguration.authMode ã 'cookie' ã«èšå®ãããã¹ãã¢ããªã§ cookie-parser ãç»é²ããäžèšã®ããã« context.authenticate ã getCookieTokenInfo ã«èšå®ããŠãã ããã
èªèšŒãšã³ããã€ã³ãâ
POST /api/auth/register- æ°ããã¢ã€ãã³ãã£ãã£ãç»é²POST /api/auth/login- è³æ Œæ å ±ã§ãã°ã€ã³POST /api/auth/logout- ãã°ã¢ãŠãããŠã»ãã·ã§ã³ãç¡å¹å- èªèšŒãå¿ èŠ
ãããã£ãŒã«ç®¡çãšã³ããã€ã³ãâ
POST /api/profiles- æ°ãããããã£ãŒã«ãäœæ- èªèšŒãå¿ èŠ
- 管çè ãŸãã¯ãããã£ãŒã«ã®ãªãŒããŒãäœæå¯èœ
GET /api/profiles/:profileId- IDã§ãããã£ãŒã«ãååŸ- èªèšŒãå¿ èŠ
- 管çè ãŸãã¯ãããã£ãŒã«ã®ãªãŒããŒãé²èЧå¯èœ
PATCH /api/profiles/:profileId- ãããã£ãŒã«ãæŽæ°- èªèšŒãå¿ èŠ
- 管çè ãŸãã¯ãããã£ãŒã«ã®ãªãŒããŒãæŽæ°å¯èœ
DELETE /api/profiles/:profileId- ãããã£ãŒã«ãåé€- èªèšŒãå¿ èŠ
- 管çè ãŸãã¯ãããã£ãŒã«ã®ãªãŒããŒãåé€å¯èœ
GET /api/profiles- ãããã£ãŒã«ã®äžèŠ§è¡šç€º/ãã£ã«ã¿ãŒ- èªèšŒãå¿ èŠ
- 管çè ã®ã¿
5ïžâ£ åæãµãŒãã¹ã®ãã¹ãâ
# æ°ããã¢ã€ãã³ãã£ãã£ãç»é²
curl -X POST http://localhost:8089/api/auth/register \
-H 'Content-Type: application/json' \
-d '{
"email": "user@example.com",
"password": "securepass123"
}'
# è³æ Œæ
å ±ã§ãã°ã€ã³
curl -X POST http://localhost:8089/api/auth/login \
-H 'Content-Type: application/json' \
-d '{
"email": "user@example.com",
"password": "securepass123"
}'
# ãããã£ãŒã«ãäœæïŒç»é²/ãã°ã€ã³ããidentityIdãå¿
èŠãšããŸãïŒ
curl -X POST http://localhost:8089/api/profiles \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer <access-token>' \
-d '{
"identityId": "6dcdd50a-e0e6-445d-82e1-3da35bc2c149",
"name": "John Doe"
}'
# IDã§ãããã£ãŒã«ãååŸ
curl -X GET http://localhost:8089/api/profiles/PROFILE_ID \
-H 'Authorization: Bearer <access-token>'
# ãããã£ãŒã«ãæŽæ°
curl -X PATCH http://localhost:8089/api/profiles/PROFILE_ID \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer <access-token>' \
-d '{
"name": "John Smith"
}'
# ãããã£ãŒã«ãåé€
curl -X DELETE http://localhost:8089/api/profiles/PROFILE_ID \
-H 'Authorization: Bearer <access-token>'
6ïžâ£ ç°å¢èšå®â
æ¬çªç°å¢ã§ã¯ãèšå®ãå€éšåãã¹ãã§ãïŒ
export const config = {
database: {
url: process.env.MONGODB_URI || 'mongodb://localhost:27017/?authSource=admin',
name: process.env.MONGODB_DB_NAME || 'dev',
user: process.env.DB_USER || 'user',
password: process.env.DB_PASSWORD || 'password',
},
storage: {
gcpProjectId: process.env.GCP_PROJECT_ID || 'your-gcp-project-id',
bucketName: process.env.GCP_BUCKET_NAME || 'your-bucket-name',
},
auth: {
encSecret: process.env.AUTH_ENC_SECRET || 'your-encryption-secret',
signSecret: process.env.AUTH_SIGN_SECRET || 'your-signing-secret',
maxFailedAttempts: parseInt(process.env.MAX_FAILED_ATTEMPTS || '5', 10),
accessTokenExpiresIn: process.env.ACCESS_TOKEN_EXPIRE || '2h',
refreshTokenExpiresIn: process.env.REFRESH_TOKEN_EXPIRE || '2d',
},
server: {
port: parseInt(process.env.PORT || '8089', 10),
},
};
ãã®åŸãåæãµãŒãã¹ãæŽæ°ïŒ
import {drivers} from '@nodeblocks/backend-sdk';
import {config} from './config';
const {withMongo, createFileStorageDriver} = drivers;
const connectToDatabase = withMongo(
config.database.url,
config.database.name,
config.database.user,
config.database.password,
);
const fileStorageDriver = createFileStorageDriver(config.storage.gcpProjectId, config.storage.bucketName);
const context = {
dataStores: {
...(await connectToDatabase('identities')),
...(await connectToDatabase('profiles')),
},
configuration: {
authSecrets: {
authEncSecret: config.auth.encSecret,
authSignSecret: config.auth.signSecret,
},
maxFailedLoginAttempts: config.auth.maxFailedAttempts,
accessTokenSignOptions: {expiresIn: config.auth.accessTokenExpiresIn},
refreshTokenSignOptions: {expiresIn: config.auth.refreshTokenExpiresIn},
identity: {
typeIds: {
admin: '100',
guest: '000',
regular: '001',
},
},
},
fileStorageDriver,
};
// ... ãµãŒãã¹ã®ç¶ã
ð ãã¹ããã©ã¯ãã£ã¹â
1. ãã¡ã€ã³å¥ã«æŽçâ
é¢é£ãããã£ãŒãã£ãŒãã°ã«ãŒãåïŒ
// â
è¯ã: ããžã«ã«ãªã°ã«ãŒãå
const authServiceMiddleware = compose(registerCredentialsFeature, loginWithCredentialsFeature, logoutFeature);
const profileServiceMiddleware = compose(
createProfileFeature,
getProfileFeature,
editProfileFeature,
deleteProfileFeature,
findProfilesFeature,
);
// â é¿ãã: æ··ãã£ãé¢å¿
const mixedMiddleware = compose(
registerCredentialsFeature,
createProfileFeature,
loginWithCredentialsFeature,
getProfileFeature,
);
â¡ïž 次ã®ã¹ãããâ
ä»ãåæãµãŒãã¹ã以äžã§æ¡åŒµã§ããŸãïŒ
- ããå€ãã®ãµãŒãã¹ã远å - 補åããªãŒããŒããŸãã¯éç¥ãã£ãŒãã£ãŒãå«ãã
- ããã«ãŠã§ã¢ã®å®è£ - ãã®ã³ã°ãã¬ãŒããªãããã£ã³ã°ããŸãã¯CORSã远å
- ã«ã¹ã¿ã ãã£ãŒãã£ãŒã®å®è£ - ãã¡ã€ã³åºæã®ããžãã¹ããžãã¯ãäœæ
- ã¹ããŒãã®ãªãŒããŒã©ã€ã - çµã¿èŸŒã¿ãã£ãŒãã£ãŒã®ããªããŒã·ã§ã³ãã«ã¹ã¿ãã€ãº
- ãã€ã¯ããµãŒãã¹ã®å®è£ - å¿ èŠã«å¿ããŠå¥ã ã®ãµãŒãã¹ã«åå²
ð é¢é£ãªã³ã¯â
- ã«ã¹ã¿ã ãµãŒãã¹ã®äœæ - åã ã®ãµãŒãã¹ã®æ§ç¯æ¹æ³ãåŠã¶
- æ¢åã¹ããŒãã®ãªãŒããŒã©ã€ã - çµã¿èŸŒã¿ããªããŒã·ã§ã³ãã«ã¹ã¿ãã€ãº
- ãããã£ãŒã«ãµãŒãã¹ - å®å šãªãããã£ãŒã«APIãªãã¡ã¬ã³ã¹
- èªèšŒãµãŒãã¹ - å®å šãªèªèšŒAPIãªãã¡ã¬ã³ã¹
- ãµãŒãã¹ã³ã³ããŒãã³ã - ãµãŒãã¹ã¢ãŒããã¯ãã£ã®çè§£
- ãã£ãŒãã£ãŒã³ã³ããŒãã³ã - ãã£ãŒãã£ãŒåæã®åŠç¿
- ãšã©ãŒåŠç - åæãµãŒãã¹ã§ã®ãšã©ãŒåŠç