Skip to content

Commit

Permalink
Production Release (#522)
Browse files Browse the repository at this point in the history
Includes:
Issue #498 Dependabot Alert: NPM IP package vulnerable to Server-Side Request Forgery (SSRF) attacks
Issue #181 Administrator | Edit User
Issue #508 Footer Changes for SmartPay Training Site
Issue #504 Update A/OPC Training Content Lesson 3
  • Loading branch information
felder101 authored Mar 27, 2024
1 parent bb1a7da commit 4aaad5f
Show file tree
Hide file tree
Showing 17 changed files with 17,916 additions and 15,381 deletions.
32,469 changes: 17,297 additions & 15,172 deletions training-front-end/package-lock.json

Large diffs are not rendered by default.

369 changes: 202 additions & 167 deletions training-front-end/src/components/AdminEditReporting.vue

Large diffs are not rendered by default.

229 changes: 229 additions & 0 deletions training-front-end/src/components/AdminEditUserDetails.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,229 @@
<script setup>
import ValidatedInput from "./ValidatedInput.vue";
import {onBeforeMount, reactive, ref, watch} from "vue";
import {helpers, required, requiredIf} from "@vuelidate/validators";
import {useVuelidate} from "@vuelidate/core";
import ValidatedSelect from "./ValidatedSelect.vue";
import {useStore} from "@nanostores/vue";
import {agencyList, bureauList, setSelectedAgencyId} from "../stores/agencies.js";
import {profile} from "../stores/user.js";
import USWDSAlert from "./USWDSAlert.vue";
import SpinnerGraphic from "./SpinnerGraphic.vue";
const props = defineProps({
userToEdit: {
type: Object,
required: true,
}
})
const user = useStore(profile)
const base_url = import.meta.env.PUBLIC_API_BASE_URL
const {withMessage} = helpers
const agency_options = useStore(agencyList)
const bureaus = useStore(bureauList)
const is_saving = ref(false)
const error = ref()
const show_error = ref(false)
const show_spinner = ref(false)
const emit = defineEmits(['cancel', 'completeUserUpdate'])
const currentUserAgencyId = agency_options.value.find(agency => agency.name === props.userToEdit.agency.name).id
const user_input = reactive({
name: props.userToEdit.name,
email: props.userToEdit.email,
agency_id: currentUserAgencyId,
bureau_id: props.userToEdit.agency_id
})
watch(() => user_input.agency_id, async () => {
setSelectedAgencyId(user_input.agency_id)
user_input.bureau_id = undefined
})
const validations_all_info = {
name: {
required: withMessage('Please enter your full name', required)
},
agency_id: {
required: withMessage('Please enter your agency', required),
},
bureau_id: {
requiredIf: withMessage('Please enter your Sub-Agency, Organization, or Bureau', requiredIf(() => bureaus.value.length)),
}
}
const v_all_info$ = useVuelidate(validations_all_info, user_input)
onBeforeMount(async () => {
// Preselect agency based on the current user agency value.
setSelectedAgencyId(user_input.agency_id)
})
async function update_user_info() {
error.value = ref()
show_error.value = false
const isFormValid = await v_all_info$.value.$validate()
if (!isFormValid) {
return
}
is_saving.value = true
show_spinner.value = true
// When user has choosen a bureau use that id instead of the agency
let {bureau_id, ...user_data} = user_input
if (bureau_id) {
user_data.agency_id = bureau_id
}
const apiURL = new URL(`${base_url}/api/v1/users/${props.userToEdit.id}`)
let response = ref();
try {
response.value = await fetch(apiURL, {
method: 'PATCH',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${user.value.jwt}`
},
body: JSON.stringify(user_data)
})
} catch (error) {
setError({
name: 'Server Error',
message: 'Sorry, we had an error connecting to the server.'
})
is_saving.value = false
show_spinner.value = false
return
}
if (!response.value.ok) {
is_saving.value = false
show_spinner.value = false
if (response.value.status === 400) {
setError({
name: 'Unauthorized',
message: 'You are not authorized to edit profile.'
})
return
}
if (response.value.status === 403) {
setError({
name: 'Unauthorized',
message: "You can not update your own profile."
})
return
}
setError({
name: 'Error',
message: "Error contacting server."
})
return
}
is_saving.value = false
show_spinner.value = false
let updatedUser = await response.value.json()
let successMessage = `Successfully updated ${updatedUser.email}`
emit('completeUserUpdate', successMessage)
}
function setError(event) {
error.value = event
show_error.value = true
}
</script>

<template>
<div class="usa-prose">
<h3>
Edit User Profile
</h3>
</div>
<USWDSAlert
v-if="show_error"
status="error"
:heading="error.name"
>
{{ error.message }}
</USWDSAlert>
<form
class="margin-bottom-3"
@submit.prevent="update_user_info"
>
<div class="grid-row grid-gap">
<div class="tablet:grid-col">
<ValidatedInput
v-model="user_input.name"
client:load
:validator="v_all_info$.name"
label="Full Name"
name="name"
/>
</div>
<div class="tablet:grid-col">
<label
for="input-email"
class="usa-label"
>
Email
</label>
<input
id="input-email"
class="usa-input bg-base-lightest"
name="input-email"
:value="userToEdit.email"
:readonly="true"
>
</div>
</div>
<div class="grid-row grid-gap">
<div class="tablet:grid-col">
<ValidatedSelect
v-model="user_input.agency_id"
client:load
:validator="v_all_info$.agency_id"
:options="agency_options"
label="Agency / organization"
name="agency"
/>
</div>
<div class="tablet:grid-col">
<ValidatedSelect
v-if="bureaus.length"
v-model="user_input.bureau_id"
client:load
:validator="v_all_info$.bureau_id"
:options="bureaus"
label="Sub-Agency, Organization, or Bureau"
name="bureau"
/>
</div>
</div>
<div class="grid-row grid-gap margin-top-3">
<div class="tablet:grid-col">
<input
class="usa-button"
type="submit"
value="Save Profile"
:disabled="is_saving"
>
<button
id="cancel"
type="button"
class="usa-button usa-button--outline"
:disabled="is_saving"
@click="$emit('cancel')"
>
Cancel
</button>
<div
v-if="show_spinner"
class="display-none tablet:display-block tablet:grid-col-1 tablet:padding-top-3 tablet:margin-left-neg-1"
>
<SpinnerGraphic />
</div>
</div>
</div>
</form>
</template>
37 changes: 33 additions & 4 deletions training-front-end/src/components/AdminSearchUser.vue
Original file line number Diff line number Diff line change
Expand Up @@ -16,22 +16,25 @@
const report_url = `${base_url}/api/v1/users/`
const update_url = `${base_url}/api/v1/users/edit-user-for-reporting/`
const currentPage = ref(0)
const numberOfResults = ref(0)
let currentPage = ref(0)
let numberOfResults = ref(0)
const numberOfPages = computed(() => Math.ceil(numberOfResults.value/PAGE_SIZE))
const searchTerm = ref('')
let searchTerm = ref('')
const selectedUser = ref()
const searchResults = ref([])
let searchResults = ref([])
const noResults = ref(false)
const error = ref()
const showSuccessMessage = ref(false)
const successMessage = ref()
async function setPage(page) {
currentPage.value = page
await search()
}
async function search() {
clearAlerts()
noResults.value = false
const url = new URL(`${report_url}`)
url.search = new URLSearchParams({searchText: searchTerm.value, page_number: currentPage.value + 1})
Expand Down Expand Up @@ -94,6 +97,22 @@
setCurrentUser(undefined)
setSelectedAgencyId(undefined)
}
async function updateUserSuccess(message) {
successMessage.value = message
showSuccessMessage.value = true
cancelEdit()
currentPage.value = 0
numberOfResults.value = 0
searchTerm.value = ''
searchResults.value = []
}
function clearAlerts() {
error.value = undefined
successMessage.value = undefined
showSuccessMessage.value = false
}
</script>

<template>
Expand All @@ -105,6 +124,14 @@
>
{{ error.message }}
</USWDSAlert>
<USWDSAlert
v-if="showSuccessMessage"
status="success"
class="usa-alert--slim"
:has-heading="false"
>
{{ successMessage }}
</USWDSAlert>
<div
v-if="!selectedUser"
class="grid-row"
Expand Down Expand Up @@ -139,6 +166,7 @@
<button
class="usa-button"
type="submit"
:disabled="!searchTerm"
>
<span class="usa-search__submit-text">
Search
Expand Down Expand Up @@ -174,6 +202,7 @@
:user="selectedUser"
@save="updateUserReports"
@cancel="cancelEdit"
@user-update-success="updateUserSuccess"
/>
</div>
</div>
Expand Down
2 changes: 1 addition & 1 deletion training-front-end/src/components/USAIdentifier.astro
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ import gsa_logo from '../assets/images/gsa-logo.svg'
<li class="usa-identifier__required-links-item">
<a href="https://www.gsa.gov/website-information/accessibility-aids"
class="usa-identifier__required-link usa-link"
>Accessibility support</a
>Accessibility statement</a
>
</li>
<li class="usa-identifier__required-links-item">
Expand Down
2 changes: 1 addition & 1 deletion training-front-end/src/components/ValidatedInput.vue
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@
</span>
<input
:id="name"
class="usa-input usa-input tablet:grid-col-8"
class="usa-input usa-input"
:class="{ 'usa-input--error':validator.$error, 'error-focus': validator.$error }"
:name="name"
:value="modelValue"
Expand Down
2 changes: 1 addition & 1 deletion training-front-end/src/components/ValidatedSelect.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
const props = defineProps({
'modelValue': {
type: String,
type: [String, Number],
required: false,
default: undefined
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,15 @@ describe('AdminAgencySelect', async () => {
it('displays the basic user information', async () => {
const props = {user: users[0]}
const wrapper = mount(AdminEditReporting, {props})
const name = wrapper.find('input[id="input-full-name"]')
const email = wrapper.find('input[id="input-email"]')
const agency = wrapper.find('input[id="input-agency"]')
const bureau = wrapper.find('input[id="input-bureau"]')

expect(name.element.value).toBe('Hugh Steeply')
expect(email.element.value).toBe('[email protected]')
expect(agency.element.value).toBe('Office of Unspecified Services')
expect(bureau.element.value).toBe('Secret Service')
const name = wrapper.find('dd[id="user-name-value"]')
const email = wrapper.find('dd[id="user-email-value"]')
const agency = wrapper.find('dd[id="user-agency-organization-value"]')
const bureau = wrapper.find('dd[id="user-bureau-value"]')

expect(name.text()).toContain('Hugh Steeply')
expect(email.text()).toContain('[email protected]')
expect(agency.text()).toContain('Office of Unspecified Services')
expect(bureau.text()).toContain('Secret Service')
})

it('displays table with header and user reporting agencies', async () => {
Expand Down
15 changes: 9 additions & 6 deletions training-front-end/src/content/training_purchase/lesson05.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -119,12 +119,15 @@ When a convenience check is used to purchase services, the Internal Revenue Serv
The IRS states that agencies may rely on the merchant category code (MCC) in determining whether a transaction is subject to Form 1099 reporting. Failure to file a correct information return [Form 1099](https://www.irs.gov/forms-pubs/about-form-1099-misc) by the due date may result in a penalty imposed by the IRS.

## What are Government-to-Government transactions?
Government-to-government transactions are payments between different agencies (inter-governmental) or payments within the same agency (intra-governmental). In most instances, these transactions are classified under Merchant Category Code 9399, Miscellaneous Government Services.
Government-to-government -- also referred to as "intra-governmental" -- transactions are payments between government entities, including within the same agency. These transactions may be classified under Merchant Category Code 9399, Miscellaneous Government Services.

Effective October 1, 2022, the general limit on charge card payments within the same agency (intra-governmental) is set at $9,999.99. The general limit on charge card payments between different federal agencies (inter-governmental) remains $24,999.99. The government’s card acceptance policies can be found in [Treasury Financial Manual (TFM) Vol. I, Part 5, Chapter 7000](https://tfm.fiscal.treasury.gov/v1/p5/c700.html). It also addresses limitations on credit card transactions.
Effective October 1, 2022, the following apply:

A few notes to keep in mind:
- The inter-governmental transaction card limit is $9,999.99, meaning no individual card transaction can exceed this limit.
- The maximum daily limit from a single payor is $24,999.99.
- Total monthly transactions, based on a 30 day rolling period, from a single payor can be no more than $100,000.00.

- Customers cannot divide an inter-governmental transaction into smaller pieces to evade this limit. For example, a buyer cannot make two purchases of $15,000 to avoid the $24,999.99 limit.
- Customers cannot divide an intra-governmental transaction into smaller pieces to evade this limit. For example, a buyer cannot make two purchases of $6,000 to avoid the $9,999.99 limit.
- Treasury retains the option to change limits. One goal is to reduce the fees the government pays when it accepts the purchase card/account for large transactions.
The government’s card acceptance policies can be found in [Treasury Financial Manual (TFM) Vol. I, Part 5, Chapter 7000](https://tfm.fiscal.treasury.gov/v1/p5/c700). It also addresses limitations on credit card transactions. Additionally, consider the following:

- Customers cannot divide transactions into smaller pieces to evade these limits.
- Treasury retains the option to change limits. One goal is to reduce the fees the government pays when it accepts the purchase card/account for large transactions.
Loading

0 comments on commit 4aaad5f

Please sign in to comment.