feat(web,server): manage authorized devices (#2329)

* feat: manage authorized devices

* chore: open api

* get header from mobile app

* write header from mobile app

* styling

* fix unit test

* feat: use relative time

* feat: update access time

* fix: tests

* chore: confirm wording

* chore: bump test coverage thresholds

* feat: add some icons

* chore: icon tweaks

---------

Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
This commit is contained in:
Jason Rasmussen
2023-04-25 22:19:23 -04:00
committed by GitHub
parent aa91b946fa
commit b8313abfa8
41 changed files with 1209 additions and 93 deletions

View File

@@ -9,12 +9,21 @@ export class UserTokenEntity {
@Column({ select: false })
token!: string;
@Column()
userId!: string;
@ManyToOne(() => UserEntity)
user!: UserEntity;
@CreateDateColumn({ type: 'timestamptz' })
createdAt!: string;
createdAt!: Date;
@UpdateDateColumn({ type: 'timestamptz' })
updatedAt!: string;
updatedAt!: Date;
@Column({ default: '' })
deviceType!: string;
@Column({ default: '' })
deviceOS!: string;
}

View File

@@ -0,0 +1,21 @@
import { MigrationInterface, QueryRunner } from 'typeorm';
export class FixNullableRelations1682371561743 implements MigrationInterface {
name = 'FixNullableRelations1682371561743';
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`ALTER TABLE "user_token" DROP CONSTRAINT "FK_d37db50eecdf9b8ce4eedd2f918"`);
await queryRunner.query(`ALTER TABLE "user_token" ALTER COLUMN "userId" SET NOT NULL`);
await queryRunner.query(
`ALTER TABLE "user_token" ADD CONSTRAINT "FK_d37db50eecdf9b8ce4eedd2f918" FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`,
);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`ALTER TABLE "user_token" DROP CONSTRAINT "FK_d37db50eecdf9b8ce4eedd2f918"`);
await queryRunner.query(`ALTER TABLE "user_token" ALTER COLUMN "userId" DROP NOT NULL`);
await queryRunner.query(
`ALTER TABLE "user_token" ADD CONSTRAINT "FK_d37db50eecdf9b8ce4eedd2f918" FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`,
);
}
}

View File

@@ -0,0 +1,16 @@
import { MigrationInterface, QueryRunner } from "typeorm";
export class AddDeviceInfoToUserToken1682371791038 implements MigrationInterface {
name = 'AddDeviceInfoToUserToken1682371791038'
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`ALTER TABLE "user_token" ADD "deviceType" character varying NOT NULL DEFAULT ''`);
await queryRunner.query(`ALTER TABLE "user_token" ADD "deviceOS" character varying NOT NULL DEFAULT ''`);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`ALTER TABLE "user_token" DROP COLUMN "deviceOS"`);
await queryRunner.query(`ALTER TABLE "user_token" DROP COLUMN "deviceType"`);
}
}

View File

@@ -6,24 +6,40 @@ import { IUserTokenRepository } from '@app/domain/user-token';
@Injectable()
export class UserTokenRepository implements IUserTokenRepository {
constructor(
@InjectRepository(UserTokenEntity)
private userTokenRepository: Repository<UserTokenEntity>,
) {}
constructor(@InjectRepository(UserTokenEntity) private repository: Repository<UserTokenEntity>) {}
async get(userToken: string): Promise<UserTokenEntity | null> {
return this.userTokenRepository.findOne({ where: { token: userToken }, relations: { user: true } });
getByToken(token: string): Promise<UserTokenEntity | null> {
return this.repository.findOne({ where: { token }, relations: { user: true } });
}
async create(userToken: Partial<UserTokenEntity>): Promise<UserTokenEntity> {
return this.userTokenRepository.save(userToken);
getAll(userId: string): Promise<UserTokenEntity[]> {
return this.repository.find({
where: {
userId,
},
relations: {
user: true,
},
order: {
updatedAt: 'desc',
createdAt: 'desc',
},
});
}
async delete(id: string): Promise<void> {
await this.userTokenRepository.delete(id);
create(userToken: Partial<UserTokenEntity>): Promise<UserTokenEntity> {
return this.repository.save(userToken);
}
save(userToken: Partial<UserTokenEntity>): Promise<UserTokenEntity> {
return this.repository.save(userToken);
}
async delete(userId: string, id: string): Promise<void> {
await this.repository.delete({ userId, id });
}
async deleteAll(userId: string): Promise<void> {
await this.userTokenRepository.delete({ user: { id: userId } });
await this.repository.delete({ userId });
}
}