Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Jwt authentication #104

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13,663 changes: 13,663 additions & 0 deletions package-lock.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
"dotenv": "^10.0.0",
"express": "^4.17.1",
"joi": "^17.4.1",
"jsonwebtoken": "^8.5.1",
"lodash.template": "^4.5.0",
"mongodb": "^4.0.0",
"pino": "^6.12.0",
Expand Down
10 changes: 7 additions & 3 deletions src/_boot/appModules.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
import { articleModule, ArticleRegistry } from '@/article';
import { ArticleMessages } from '@/article/messages';
import { authModule, AuthRegistry } from '@/auth';
import { JWTSignerConfig } from '@/auth/infrastructure/JWTSignerService';
import { commentModule, CommentRegistry } from '@/comment';
import { KeyPairConfig } from '@/_lib/certificates';

type AppModulesMessages = ArticleMessages;

type AppModulesConfig = {};
type AppModulesConfig = KeyPairConfig & JWTSignerConfig;
//eu estou trazendo o jwt config direto do auth/infra isso está certo?

const appModules = [articleModule, commentModule];
const appModules = [articleModule, commentModule, authModule];

type AppModulesRegistry = ArticleRegistry & CommentRegistry;
type AppModulesRegistry = ArticleRegistry & CommentRegistry & AuthRegistry;

export { appModules };
export type { AppModulesMessages, AppModulesConfig, AppModulesRegistry };
24 changes: 24 additions & 0 deletions src/_boot/certificate.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// EM DESENVOLVIMENTO
type CertificateConfig = {
certificate: {
publicKey: string;
};
};
//vai ter keypair e verifier aqui
// no auth vai ter só o sign
const certificate = makeModule('certificate', async ({ container: { register }, config }) => {
const publicKey = makePublicKey(config.certificate);

register({
publicKey: asValue(publicKey),
verifier: asFunction(makeSecurityPassVerifier),
});
});

type CertificateRegistry = {
publicKey: PublicKey;
verifier: Verifier;
};

export { certificate };
export type { CertificateConfig, CertificateRegistry };
3 changes: 1 addition & 2 deletions src/_lib/WithInvariants.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
import { AggregateRoot } from '@/_lib/DDD';
import assertFn from 'assert';

type AssertionFn = (value: any, message?: string | Error) => void;

type InvariantCheckFn<A> = (self: A, assert: AssertionFn) => void;

const makeWithInvariants =
<A extends AggregateRoot<any>>(invariantCheckFn: InvariantCheckFn<A>) =>
<A>(invariantCheckFn: InvariantCheckFn<A>) =>
<F extends (...args: any[]) => A>(fn: F) =>
(...args: Parameters<F>): ReturnType<F> => {
const self = fn(...args);
Expand Down
47 changes: 47 additions & 0 deletions src/_lib/certificates/KeyPair.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { makePrivateKey, PrivateKey } from './PrivateKey';
import { makePublicKey, PublicKey } from './PublicKey';
import { logger } from '../logger';

type KeyPair = {
publicKey: PublicKey;
privateKey: PrivateKey;
};

type KeyPairConfig = {
keyPair: {
publicKey: string;
privateKey: string;
passphrase?: string;
};
};

type Dependencies = {
config: KeyPairConfig;
};

const makeKeyPair = ({ config }: Dependencies): KeyPair => {
let publicKey: PublicKey;
let privateKey: PrivateKey;

try {
privateKey = makePrivateKey(config.keyPair);
} catch (err) {
logger.error({ message: '[KeyPair] Unable to load private key', err });
throw err;
}

try {
publicKey = makePublicKey(config.keyPair);
} catch (err) {
logger.error({ message: '[KeyPair] Unable to load public key', err });
throw err;
}

return {
publicKey,
privateKey,
};
};

export { makeKeyPair };
export type { KeyPairConfig, KeyPair };
37 changes: 37 additions & 0 deletions src/_lib/certificates/PrivateKey.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import assert from 'assert';
import { readFileSync } from 'fs';

type PrivateKey = Buffer | { key: Buffer; passphrase: string };

type PrivateKeyProps = {
privateKey: string;
passphrase?: string;
};

const makePrivateKey = (props: PrivateKeyProps): PrivateKey => {
let passphrase: string | undefined;
let privateKey: Buffer;

if (props.passphrase) {
const passphraseBuffer = Buffer.from(props.passphrase, 'base64');
console.log('chegou aqui');
assert(passphraseBuffer.toString('base64') !== props.passphrase, 'Keypair passphrase should be base64 encoded');

passphrase = passphraseBuffer.toString('utf-8');
}

if (props.privateKey.match('^(.*)BEGIN(.*)PRIVATE KEY')) {
privateKey = Buffer.from(props.privateKey);
} else {
privateKey = readFileSync(props.privateKey);
}
return passphrase
? {
passphrase,
key: privateKey,
}
: privateKey;
};

export { makePrivateKey };
export type { PrivateKey };
22 changes: 22 additions & 0 deletions src/_lib/certificates/PublicKey.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { readFileSync } from 'fs';

type PublicKey = Buffer;

type PublicKeyProps = {
publicKey: string;
};

const makePublicKey = (props: PublicKeyProps): PublicKey => {
let publicKey: Buffer;

if (props.publicKey.match('^(.*)BEGIN(.*)PUBLIC KEY')) {
publicKey = Buffer.from(props.publicKey);
} else {
publicKey = readFileSync(props.publicKey);
}

return publicKey;
};

export { makePublicKey };
export type { PublicKey };
3 changes: 3 additions & 0 deletions src/_lib/certificates/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export * from './KeyPair';
export * from './PrivateKey';
export * from './PublicKey';
35 changes: 35 additions & 0 deletions src/_lib/security/Credentials.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { makeWithInvariants } from '../WithInvariants';

namespace Credentials {
type Credentials<M extends Record<string, any> = Record<string, any>, R = string> = Readonly<{
principalId: string;
role: R[];
meta: M;
emittedAt?: Date;
}>;

const withInvariants = makeWithInvariants<Credentials>((self, assert) => {
assert(self.meta, 'Credentials metadata is obligatory');
});

type CredentialsProps<M extends Record<string, any> = Record<string, any>, R = string> = Readonly<{
principalId: string;
role: R[];
meta: M;
}>;

export const create = withInvariants(
<M extends Record<string, any> = Record<string, any>, R = string>(
props: CredentialsProps<M, R>
): Credentials<M, R> => ({
principalId: props.principalId,
role: props.role,
meta: props.meta,
emittedAt: new Date(),
})
);

export type Type<M extends Record<string, any> = Record<string, any>, R = string> = Credentials<M, R>;
}

export { Credentials };
18 changes: 18 additions & 0 deletions src/_lib/security/SecurityBooth.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { Credentials } from './Credentials';

type SecurityPass = {
token: string;
emittedAt: Date;
expiresAt: Date;
};

type Signer = (credentials: Credentials.Type) => Promise<SecurityPass>;

type Verifier = (token: string) => Promise<Credentials.Type>;

type SecurityBooth = {
sign: Signer;
verify: Verifier;
};

export { SecurityPass, Signer, Verifier, SecurityBooth };
2 changes: 2 additions & 0 deletions src/_lib/security/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './Credentials';
export * from './SecurityBooth';
19 changes: 19 additions & 0 deletions src/auth/_lib/security/Credentials.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { UnauthorizedError } from '@/_lib/errors/UnauthorizedError';
import { Credentials } from '@/_lib/security';
//pode ir pro sharedKernel
type WriterCredentials = Credentials.Type<
{
writerId: string;
},
'writer'
>;

const ensureRole: (credentials: unknown) => asserts credentials is WriterCredentials = (credentials: any) => {
const isWriterCreds = credentials && credentials.role === 'writer';

if (!isWriterCreds) {
throw UnauthorizedError.create();
}
};

export { ensureRole };
32 changes: 32 additions & 0 deletions src/auth/application/useCases/Authenticate.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { Credentials, SecurityBooth, SecurityPass } from '@/_lib/security';
import { ApplicationService } from '@/_lib/DDD';
import { UserRepository } from '@/auth/domain/UserRepository';

type Dependencies = {
signerService: SecurityBooth;
userRepository: UserRepository;
};

type AuthenticationDTO = {
username: string;
password: string;
};

type Authenticate = ApplicationService<AuthenticationDTO, SecurityPass>;

const makeAuthenticate =
({ signerService, userRepository }: Dependencies): Authenticate =>
async ({ password, username }) => {
const user = await userRepository.findByUsername(username);
//verificar senha, desencriptar e verificar
const credentials = Credentials.create({
principalId: user.username,
role: user.roles,
meta: { userId: user.id.value },
});

return signerService.sign(<Credentials.Type<Record<string, any>, string>>credentials);
};

export { makeAuthenticate };
export type { Authenticate };
35 changes: 35 additions & 0 deletions src/auth/application/useCases/CreateUser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { User } from '@/auth/domain/User';
import { UserRepository } from '@/auth/domain/UserRepository';
import { ApplicationService } from '@/_lib/DDD';

type Dependencies = {
userRepository: UserRepository;
};

type CreateUserDTO = Readonly<{
username: string;
password: string;
roles: string[];
}>;

type CreateUser = ApplicationService<CreateUserDTO, string>;

const makeCreateUser =
({ userRepository }: Dependencies): CreateUser =>
async (payload) => {
const id = await userRepository.getNextId();

const user = User.create({
id,
username: payload.username,
password: payload.password,
roles: payload.roles,
});

await userRepository.store(user);

return id.value;
};

export { makeCreateUser };
export type { CreateUser };
42 changes: 42 additions & 0 deletions src/auth/domain/User.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { UserId } from '@/auth/domain/UserId';
import { AggregateRoot } from '@/_lib/DDD';

namespace User {
type Status = 'ACTIVE' | 'DELETED';

type User = AggregateRoot<UserId> &
Readonly<{
username: string;
password: string;
roles: string[];
status: Status;
createdAt: Date;
updatedAt: Date;
version: number;
}>;

type UserProps = Readonly<{
id: UserId;
username: string;
password: string;
roles: string[];
}>;

export const create = (props: UserProps): User => ({
...props,
status: 'ACTIVE',
createdAt: new Date(),
updatedAt: new Date(),
version: 0,
});

export const markAsDeleted = (self: User): User => ({
...self,
username: `${self.username}.${Date.now()}`,
status: 'DELETED',
});

export type Type = User;
}

export { User };
5 changes: 5 additions & 0 deletions src/auth/domain/UserId.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { AggregateId } from '@/_lib/DDD';

type UserId = AggregateId<string>;

export { UserId };
8 changes: 8 additions & 0 deletions src/auth/domain/UserRepository.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { Repository } from '@/_lib/DDD';
import { User } from './User';

type UserRepository = Repository<User.Type> & {
findByUsername(username: string): Promise<User.Type>;
};

export { UserRepository };
Loading