Skip to content

Commit f5b0995

Browse files
authored
Release 1.83 [to release] (#2409)
* Merge pull request #2402 from opengovsg/feat/add-safebrowsing-expiry feat: add safeBrowsingExpiry column to urls table * feat: check for malicious link just before redirect (#2403) * feat: check for malicious link just before redirect * fix: do not update as JSON object yet * fix: update method signature * 1.83.0
2 parents 15c76ba + 3997a79 commit f5b0995

File tree

33 files changed

+889
-227
lines changed

33 files changed

+889
-227
lines changed

CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,16 @@ All notable changes to this project will be documented in this file. Dates are d
44

55
Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
66

7+
#### [v1.83.0](https://github.com/opengovsg/GoGovSG/compare/v1.82.0...v1.83.0)
8+
9+
- feat: check for malicious link just before redirect [`#2403`](https://github.com/opengovsg/GoGovSG/pull/2403)
10+
- feat: add safeBrowsingExpiry column to urls table [`#2402`](https://github.com/opengovsg/GoGovSG/pull/2402)
11+
- Release 1.82.0 [to develop] [`#2405`](https://github.com/opengovsg/GoGovSG/pull/2405)
12+
713
#### [v1.82.0](https://github.com/opengovsg/GoGovSG/compare/v1.81.0...v1.82.0)
814

15+
> 31 July 2025
16+
917
#### [v1.81.0](https://github.com/opengovsg/GoGovSG/compare/v1.80.0...v1.81.0)
1018

1119
> 24 July 2025
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/** @type {import('sequelize-cli').Migration} */
2+
module.exports = {
3+
async up(queryInterface, Sequelize) {
4+
/**
5+
* Add altering commands here.
6+
*
7+
* Example:
8+
* await queryInterface.createTable('users', { id: Sequelize.INTEGER });.
9+
*/
10+
await queryInterface.addColumn('urls', 'safeBrowsingExpiry', {
11+
type: Sequelize.DATE,
12+
allowNull: true,
13+
defaultValue: null,
14+
})
15+
},
16+
17+
async down(queryInterface) {
18+
/**
19+
* Add reverting commands here.
20+
*
21+
* Example:
22+
* await queryInterface.dropTable('users');.
23+
*/
24+
await queryInterface.removeColumn('urls', 'safeBrowsingExpiry')
25+
},
26+
}

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "GoGovSG",
3-
"version": "1.82.0",
3+
"version": "1.83.0",
44
"description": "Link shortener for Singapore government.",
55
"main": "src/server/index.js",
66
"scripts": {

src/server/mappers/UrlMapper.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ export class UrlMapper implements Mapper<StorableUrl, UrlType> {
3232
contactEmail: urlType.contactEmail,
3333
source: urlType.source,
3434
tagStrings: urlType.tagStrings,
35+
safeBrowsingExpiry: urlType.safeBrowsingExpiry,
3536
}
3637
}
3738
}

src/server/models/index.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { UrlClicks } from './statistics/clicks'
88
import { syncFunctions } from './functions'
99
import { Tag } from './tag'
1010
import { Job, JobItem } from './job'
11+
import { DEV_ENV } from '../config'
1112

1213
// One user can create many urls but each url can only be mapped to one user.
1314
User.hasMany(Url, { as: 'Urls', foreignKey: { allowNull: false } })
@@ -45,6 +46,8 @@ UrlClicks.belongsTo(Url, { foreignKey: 'shortUrl' })
4546
* Initialise the database table.
4647
*/
4748
export default async () => {
48-
await sequelize.sync()
49+
if (DEV_ENV) {
50+
await sequelize.sync()
51+
}
4952
await syncFunctions()
5053
}

src/server/models/url.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ export interface UrlBaseType extends IdType {
2424
readonly description: string
2525
readonly source: StorableUrlSource
2626
readonly tagStrings: string
27+
readonly safeBrowsingExpiry: string | null
2728
}
2829

2930
export interface UrlType extends IdType, UrlBaseType, Sequelize.Model {
@@ -229,6 +230,10 @@ export const Url = <UrlTypeStatic>sequelize.define(
229230
allowNull: false,
230231
defaultValue: '',
231232
},
233+
safeBrowsingExpiry: {
234+
type: Sequelize.DATE,
235+
allowNull: true,
236+
},
232237
},
233238
{
234239
hooks: {

src/server/modules/api/admin-v1/__tests__/AdminApiV1Controller.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ const urlManagementService = {
1212
changeOwnership: jest.fn(),
1313
getUrlsWithConditions: jest.fn(),
1414
bulkCreate: jest.fn(),
15+
deactivateMaliciousShortUrl: jest.fn(),
1516
}
1617

1718
const userRepository = {

src/server/modules/api/external-v1/__tests__/ApiV1Controller.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ const urlManagementService = {
1616
changeOwnership: jest.fn(),
1717
getUrlsWithConditions: jest.fn(),
1818
bulkCreate: jest.fn(),
19+
deactivateMaliciousShortUrl: jest.fn(),
1920
}
2021

2122
const urlV1Mapper = new UrlV1Mapper()

src/server/modules/auth/__tests__/LoginController.test.ts

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,7 @@ describe('LoginController', () => {
115115
const initMailer = jest.fn()
116116
const mailJobFailure = jest.fn()
117117
const mailJobSuccess = jest.fn()
118+
const mailDeactivatedMaliciousShortUrl = jest.fn()
118119

119120
const deleteOtpByEmail = jest.fn()
120121
const setOtpForEmail = jest.fn()
@@ -123,7 +124,13 @@ describe('LoginController', () => {
123124
const urlMapper = new UrlMapper()
124125
const authService = new AuthService(
125126
{ hash, compare },
126-
{ mailOTP, initMailer, mailJobFailure, mailJobSuccess },
127+
{
128+
mailOTP,
129+
initMailer,
130+
mailJobFailure,
131+
mailJobSuccess,
132+
mailDeactivatedMaliciousShortUrl,
133+
},
127134
{ deleteOtpByEmail, setOtpForEmail, getOtpForEmail },
128135
new UserRepository(new UserMapper(urlMapper), urlMapper),
129136
)
@@ -200,6 +207,7 @@ describe('LoginController', () => {
200207
const initMailer = jest.fn()
201208
const mailJobFailure = jest.fn()
202209
const mailJobSuccess = jest.fn()
210+
const mailDeactivatedMaliciousShortUrl = jest.fn()
203211

204212
const deleteOtpByEmail = jest.fn()
205213
const setOtpForEmail = jest.fn()
@@ -218,7 +226,13 @@ describe('LoginController', () => {
218226

219227
const authService = new AuthService(
220228
{ hash, compare },
221-
{ mailOTP, initMailer, mailJobFailure, mailJobSuccess },
229+
{
230+
mailOTP,
231+
initMailer,
232+
mailJobFailure,
233+
mailJobSuccess,
234+
mailDeactivatedMaliciousShortUrl,
235+
},
222236
{ deleteOtpByEmail, setOtpForEmail, getOtpForEmail },
223237
userRepository,
224238
)
@@ -405,6 +419,7 @@ describe('LoginController', () => {
405419
const initMailer = jest.fn()
406420
const mailJobFailure = jest.fn()
407421
const mailJobSuccess = jest.fn()
422+
const mailDeactivatedMaliciousShortUrl = jest.fn()
408423
const deleteOtpByEmail = jest.fn()
409424
const setOtpForEmail = jest.fn()
410425
const getOtpForEmail = jest.fn()
@@ -421,7 +436,13 @@ describe('LoginController', () => {
421436

422437
const authService = new AuthService(
423438
{ hash, compare },
424-
{ mailOTP, initMailer, mailJobFailure, mailJobSuccess },
439+
{
440+
mailOTP,
441+
initMailer,
442+
mailJobFailure,
443+
mailJobSuccess,
444+
mailDeactivatedMaliciousShortUrl,
445+
},
425446
{ deleteOtpByEmail, setOtpForEmail, getOtpForEmail },
426447
userRepository,
427448
)

0 commit comments

Comments
 (0)