In general, TypeScript will only warn you when you're doing something that's potentially invalid or very likely to be a mistake.
In this case, you're not. You're reading a field that may be undefined, but that won't immediately cause an error, and it's a dictionary-style object which suggests that's normal behaviour.
If you do do something that's definitely dangerous, such as use that user variable and assume that it is defined, then you'll get an error. For example:
interface User {firstName: string}
interface Contact {[index: string]: User | undefined}
const contacts: Contact = {};
const randomId = '1240astat0252';
const user = contacts[randomId];
console.log(user.firstName); // Error: Object is possibly 'undefined'
For most cases, that's enough. This ensures that any code that really uses retrieved properties must first ensure their value is defined (e.g. if (user) { ... }).
If you want an error on any unknown property accesses, you need to remove the index signature. To then actually access fields with no index signature, you'll need to show TypeScript that the field definitely exists. There's a few options, but as an example you could use a custom type guard:
interface User {firstName: string}
type HasContact<N extends string> = { [name in N]: User };
function hasContact<N extends string>(x: {}, name: N): x is HasContact<N> {
return name in x;
}
const contacts = {};
const randomId = '1240astat0252';
const user = contacts[randomId]; // Error
if (hasContact(contacts, randomId)) {
const user = contacts[randomId]; // Inside the 'if' this is all good
contacts['differentId']; // Still an error
}